Files
inbuxa-server/crates/jmap-proto/src/request/parser.rs
T
jcoffey-dev 8afaee7d21
ci / fork-checks (pull_request) Successful in 15s
ci / build (pull_request) Successful in 4m41s
DLP and mail flow rules: inbuxa:MailRule over JMAP, and its permissions
Phase 2e of the DLP and mail flow rules spec, the API half.

- inbuxa:MailRule/get and /set under urn:inbuxa:jmap. Rules convert
  through serde, so what a client sends is the stored format. A create
  or change is validated whole (Rule::validate) and refused with the
  property at fault; id, createdBy, createdAt and updatedAt are the
  server's. Every change goes through the request layer's audit record.
- Six permissions, ids 674-679 (enum and schema labels): mail flow rules
  (sysMailRuleGet/Update), DLP rules (sysDlpPolicyGet/Update) and held
  mail (sysDlpReviewGet/Update, for phase 3). Either kind's permission
  gets through the gate; the handler shows and changes each rule only
  with its own kind's. All server-level: a tenant is refused (settled
  answer 3).
- Administrators get all six; the server-level Compliance Officer gets
  DLP rules to see and held mail to review (settled answer 4), added
  once to an existing server's officer role by the grant mechanism,
  which gains an officer audience.
- Privacy catalog entry for inbuxa:MailRule.

tests/src/system/mail_rules.rs: create, list in order, validation,
server-set properties refused, update, kind-separated permissions for
an officer, destroy, audit records.
2026-09-28 17:29:35 -07:00

1163 lines
51 KiB
Rust

/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*
* Modified by Coffey Labs in 2026 for INBUXA.
*/
use super::{
Call, Request, RequestMethod,
method::{MethodFunction, MethodName, MethodObject},
};
use crate::request::{
CopyRequestMethod, GetRequestMethod, ParseRequestMethod, QueryChangesRequestMethod,
QueryRequestMethod, SetRequestMethod, deserialize::DeserializeArguments,
};
use serde::{
Deserialize, Deserializer,
de::{self, SeqAccess, Visitor},
};
use std::fmt::{self, Display};
impl<'x> Request<'x> {
pub fn parse(json: &'x [u8], max_calls: usize, max_size: usize) -> trc::Result<Self> {
if json.len() <= max_size {
match serde_json::from_slice::<Request>(json) {
Ok(request) => {
if request.method_calls.len() <= max_calls {
Ok(request)
} else {
Err(trc::LimitEvent::CallsIn.into_err())
}
}
Err(err) => Err(trc::JmapEvent::NotRequest
.into_err()
.reason(err.to_string())
.details(String::from_utf8_lossy(json).into_owned())),
}
} else {
Err(trc::LimitEvent::SizeRequest.into_err())
}
}
}
impl<'de> DeserializeArguments<'de> for Request<'de> {
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"using" => {
self.using = map.next_value()?;
},
b"methodCalls" => {
self.method_calls = map.next_value()?;
},
b"createdIds" => {
self.created_ids = map.next_value()?;
},
_ => {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
);
Ok(())
}
}
struct CallVisitor;
impl<'de> Visitor<'de> for CallVisitor {
type Value = Call<RequestMethod<'de>>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("an array with 3 elements")
}
fn visit_seq<V>(self, mut seq: V) -> Result<Call<RequestMethod<'de>>, V::Error>
where
V: SeqAccess<'de>,
{
let method_name = seq
.next_element::<std::borrow::Cow<str>>()?
.ok_or_else(|| de::Error::invalid_length(0, &self))?;
let name = match MethodName::parse(method_name.as_ref()) {
Some(name) => name,
None => {
// Ignore the rest of the call
let _ = seq
.next_element::<serde::de::IgnoredAny>()?
.ok_or_else(|| de::Error::invalid_length(1, &self))?;
let id = seq
.next_element::<String>()?
.ok_or_else(|| de::Error::invalid_length(2, &self))?;
return Ok(Call {
id,
method: RequestMethod::Error(
trc::JmapEvent::UnknownMethod
.into_err()
.details(method_name.to_string()),
),
name: MethodName::error(),
});
}
};
let method = match (&name.fnc, &name.obj) {
(MethodFunction::Get, MethodObject::Email) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::Email(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Get, MethodObject::Mailbox) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::Mailbox(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Get, MethodObject::Thread) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::Thread(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Get, MethodObject::Identity) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::Identity(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Get, MethodObject::EmailSubmission) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::EmailSubmission(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Get, MethodObject::PushSubscription) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::PushSubscription(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Get, MethodObject::MaskedEmail) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::MaskedEmail(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Get, MethodObject::DeletedAccount) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::DeletedAccount(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Get, MethodObject::AiLimits) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::AiLimits(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Get, MethodObject::LogSettings) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::LogSettings(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Get, MethodObject::DataInventory) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::DataInventory(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Get, MethodObject::InventorySnapshot) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::InventorySnapshot(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Get, MethodObject::ProtocolPolicy) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::ProtocolPolicy(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Get, MethodObject::TenantProtocolPolicy) => match seq.next_element() {
Ok(Some(value)) => {
RequestMethod::Get(GetRequestMethod::TenantProtocolPolicy(value))
}
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Get, MethodObject::VacationResponse) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::VacationResponse(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Get, MethodObject::SieveScript) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::Sieve(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Get, MethodObject::Principal) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::Principal(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Get, MethodObject::Quota) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::Quota(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Get, MethodObject::Blob) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::Blob(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Get, MethodObject::Calendar) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::Calendar(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Get, MethodObject::CalendarEvent) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::CalendarEvent(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Get, MethodObject::CalendarEventNotification) => {
match seq.next_element() {
Ok(Some(value)) => {
RequestMethod::Get(GetRequestMethod::CalendarEventNotification(value))
}
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
}
}
(MethodFunction::Get, MethodObject::ParticipantIdentity) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::ParticipantIdentity(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
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::FileNode) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::FileNode(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Get, MethodObject::ShareNotification) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::ShareNotification(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),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Get, MethodObject::Registry(_)) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::Registry(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Set, MethodObject::Email) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::Email(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Set, MethodObject::Mailbox) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::Mailbox(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Set, MethodObject::Identity) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::Identity(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Set, MethodObject::EmailSubmission) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::EmailSubmission(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Set, MethodObject::PushSubscription) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::PushSubscription(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Set, MethodObject::MaskedEmail) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::MaskedEmail(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Set, MethodObject::DeletedAccount) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::DeletedAccount(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Set, MethodObject::AiLimits) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::AiLimits(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Set, MethodObject::LogSettings) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::LogSettings(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Set, MethodObject::Explanation) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::Explanation(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Set, MethodObject::ProtocolPolicy) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::ProtocolPolicy(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Set, MethodObject::TenantProtocolPolicy) => match seq.next_element() {
Ok(Some(value)) => {
RequestMethod::Set(SetRequestMethod::TenantProtocolPolicy(value))
}
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Set, MethodObject::VacationResponse) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::VacationResponse(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Set, MethodObject::SieveScript) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::Sieve(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Set, MethodObject::Calendar) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::Calendar(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Set, MethodObject::CalendarEvent) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::CalendarEvent(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Set, MethodObject::CalendarEventNotification) => {
match seq.next_element() {
Ok(Some(value)) => {
RequestMethod::Set(SetRequestMethod::CalendarEventNotification(value))
}
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
}
}
(MethodFunction::Set, MethodObject::ParticipantIdentity) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::ParticipantIdentity(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
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::Set, MethodObject::FileNode) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::FileNode(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Set, MethodObject::ShareNotification) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::ShareNotification(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Set, MethodObject::Registry(_)) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::Registry(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),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Query, MethodObject::Mailbox) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Query(QueryRequestMethod::Mailbox(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Query, MethodObject::EmailSubmission) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Query(QueryRequestMethod::EmailSubmission(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Query, MethodObject::SieveScript) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Query(QueryRequestMethod::Sieve(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Query, MethodObject::Principal) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Query(QueryRequestMethod::Principal(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Query, MethodObject::Quota) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Query(QueryRequestMethod::Quota(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Query, MethodObject::Calendar) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Query(QueryRequestMethod::Calendar(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Query, MethodObject::CalendarEvent) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Query(QueryRequestMethod::CalendarEvent(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Query, MethodObject::CalendarEventNotification) => {
match seq.next_element() {
Ok(Some(value)) => {
RequestMethod::Query(QueryRequestMethod::CalendarEventNotification(value))
}
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
}
}
(MethodFunction::Query, MethodObject::AddressBook) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Query(QueryRequestMethod::AddressBook(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
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::Query, MethodObject::FileNode) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Query(QueryRequestMethod::FileNode(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Query, MethodObject::ShareNotification) => match seq.next_element() {
Ok(Some(value)) => {
RequestMethod::Query(QueryRequestMethod::ShareNotification(value))
}
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
// inbuxa: account lock with delegation
(MethodFunction::Get, MethodObject::AccountLock) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::AccountLock(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Set, MethodObject::AccountLock) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::AccountLock(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
// inbuxa: legal hold exports
(MethodFunction::Get, MethodObject::HoldExport) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::HoldExport(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Set, MethodObject::HoldExport) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::HoldExport(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
// inbuxa: DLP and mail flow rules
(MethodFunction::Get, MethodObject::MailRule) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::MailRule(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Set, MethodObject::MailRule) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::MailRule(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
// inbuxa: legal hold
(MethodFunction::Get, MethodObject::LegalHold) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::LegalHold(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Set, MethodObject::LegalHold) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::LegalHold(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
// inbuxa: the audit log
(MethodFunction::Get, MethodObject::AuditEvent) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::AuditEvent(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Query, MethodObject::AuditEvent) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Query(QueryRequestMethod::AuditEvent(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Get, MethodObject::AuditSettings) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::AuditSettings(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Set, MethodObject::AuditSettings) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::AuditSettings(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Set, MethodObject::AuditExport) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::AuditExport(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Set, MethodObject::AuditVerification) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::AuditVerification(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Query, MethodObject::Registry(_)) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Query(QueryRequestMethod::Registry(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))
}
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::QueryChanges, MethodObject::Mailbox) => match seq.next_element() {
Ok(Some(value)) => {
RequestMethod::QueryChanges(QueryChangesRequestMethod::Mailbox(value))
}
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::QueryChanges, MethodObject::EmailSubmission) => {
match seq.next_element() {
Ok(Some(value)) => RequestMethod::QueryChanges(
QueryChangesRequestMethod::EmailSubmission(value),
),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
}
}
(MethodFunction::QueryChanges, MethodObject::Principal) => match seq.next_element() {
Ok(Some(value)) => {
RequestMethod::QueryChanges(QueryChangesRequestMethod::Principal(value))
}
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::QueryChanges, MethodObject::Quota) => match seq.next_element() {
Ok(Some(value)) => {
RequestMethod::QueryChanges(QueryChangesRequestMethod::Quota(value))
}
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::QueryChanges, MethodObject::CalendarEvent) => match seq.next_element()
{
Ok(Some(value)) => {
RequestMethod::QueryChanges(QueryChangesRequestMethod::CalendarEvent(value))
}
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::QueryChanges, MethodObject::CalendarEventNotification) => {
match seq.next_element() {
Ok(Some(value)) => RequestMethod::QueryChanges(
QueryChangesRequestMethod::CalendarEventNotification(value),
),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
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::QueryChanges, MethodObject::FileNode) => match seq.next_element() {
Ok(Some(value)) => {
RequestMethod::QueryChanges(QueryChangesRequestMethod::FileNode(value))
}
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::QueryChanges, MethodObject::ShareNotification) => {
match seq.next_element() {
Ok(Some(value)) => RequestMethod::QueryChanges(
QueryChangesRequestMethod::ShareNotification(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),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Copy, MethodObject::Email) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Copy(CopyRequestMethod::Email(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Copy, MethodObject::Blob) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Copy(CopyRequestMethod::Blob(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Copy, MethodObject::CalendarEvent) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Copy(CopyRequestMethod::CalendarEvent(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
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::Copy, MethodObject::FileNode) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Copy(CopyRequestMethod::FileNode(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),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Upload, MethodObject::Blob) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::UploadBlob(value),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Import, MethodObject::Email) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::ImportEmail(value),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Parse, MethodObject::Email) => match seq.next_element() {
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::CalendarEvent) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Parse(ParseRequestMethod::CalendarEvent(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));
}
},
(MethodFunction::GetAvailability, MethodObject::Principal) => {
match seq.next_element() {
Ok(Some(value)) => {
RequestMethod::Get(GetRequestMethod::PrincipalAvailability(value))
}
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
}
}
(MethodFunction::Validate, MethodObject::SieveScript) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::ValidateScript(value),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
(MethodFunction::Echo, MethodObject::Core) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Echo(value),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
}
},
_ => {
return Err(de::Error::custom(format!(
"Invalid method function/object combination: {}",
method_name
)));
}
};
let id = seq
.next_element::<String>()?
.ok_or_else(|| de::Error::invalid_length(2, &self))?;
Ok(Call { id, method, name })
}
}
impl RequestMethod<'_> {
fn invalid(err: impl Display) -> Self {
RequestMethod::Error(
trc::JmapEvent::InvalidArguments
.into_err()
.details(err.to_string()),
)
}
}
impl<'de> Deserialize<'de> for Request<'de> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct RequestVisitor;
impl<'de> Visitor<'de> for RequestVisitor {
type Value = Request<'de>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a JMAP request object")
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: de::MapAccess<'de>,
{
let mut target = Request::default();
let mut has_using = false;
let mut has_method_calls = false;
while let Some(key) = map.next_key::<&str>()? {
match key {
"using" => has_using = true,
"methodCalls" => has_method_calls = true,
_ => {}
}
target
.deserialize_argument(key, &mut map)
.map_err(de::Error::custom)?;
}
if !has_using || !has_method_calls {
return Err(de::Error::custom(
"Request is missing the \"using\" or \"methodCalls\" property.",
));
}
Ok(target)
}
}
deserializer.deserialize_map(RequestVisitor)
}
}
impl<'de> Deserialize<'de> for Call<RequestMethod<'de>> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_seq(CallVisitor)
}
}
#[cfg(test)]
mod tests {
use crate::request::Request;
const TEST: &str = r#"
{
"using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ],
"methodCalls": [
[ "method1", {
"arg1": "arg1data",
"arg2": "arg2data"
}, "c1" ],
[ "Core/echo", {
"hello": true,
"high": 5
}, "c2" ],
[ "method3", {"hello": [{"a": {"b": true}}]}, "c3" ]
],
"createdIds": {
"c1": "m1",
"c2": "m2"
}
}
"#;
const TEST1: &str = r#"
{
"using": [
"urn:ietf:params:jmap:core",
"urn:ietf:params:jmap:mail"
],
"methodCalls": [
[
"Email/query",
{
"accountId": "0",
"filter": { "conditions": [ { "hasKeyword": "music", "maxSize": 455 }, { "hasKeyword": "video" }, { "operator": "AND", "conditions": [ { "subject": "test" }, { "minSize": 100 } ] } ], "operator": "OR" },
"sort": [
{
"property": "subject",
"isAscending": true
},
{
"property": "allInThreadHaveKeyword",
"isAscending": false,
"keyword": "$seen"
},
{
"keyword": "$junk",
"property": "someInThreadHaveKeyword",
"collation": "i;octet",
"isAscending": false
}
],
"position": 0,
"limit": 10
},
"c1"
]
],
"createdIds": {}
}
"#;
const TEST2: &str = r##"
{
"using": [
"urn:ietf:params:jmap:submission",
"urn:ietf:params:jmap:mail",
"urn:ietf:params:jmap:core"
],
"methodCalls": [
[
"Email/set",
{
"accountId": "c",
"create": {
"c37ee58b-e224-4799-88e6-1d7484e3b782": {
"mailboxIds": {
"9": true
},
"subject": "test",
"from": [
{
"name": "Foo",
"email": "[email protected]"
}
],
"to": [
{
"name": null,
"email": "[email protected]"
}
],
"cc": [],
"bcc": [],
"replyTo": [
{
"name": null,
"email": "[email protected]"
}
],
"htmlBody": [
{
"partId": "c37ee58b-e224-4799-88e6-1d7484e3b782",
"type": "text/html"
}
],
"bodyValues": {
"c37ee58b-e224-4799-88e6-1d7484e3b782": {
"value": "<p>test email<br></p>",
"isEncodingProblem": false,
"isTruncated": false
}
},
"header:User-Agent:asText": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/113.0"
}
}
},
"c0"
],
[
"EmailSubmission/set",
{
"accountId": "c",
"create": {
"c37ee58b-e224-4799-88e6-1d7484e3b782": {
"identityId": "a",
"emailId": "#c37ee58b-e224-4799-88e6-1d7484e3b782",
"envelope": {
"mailFrom": {
"email": "[email protected]"
},
"rcptTo": [
{
"email": "[email protected]"
}
]
}
}
},
"onSuccessUpdateEmail": {
"#c37ee58b-e224-4799-88e6-1d7484e3b782": {
"mailboxIds/d": true,
"mailboxIds/9": null,
"keywords/$seen": true,
"keywords/$draft": null
}
}
},
"c1"
]
]
}
"##;
const TEST_ESCAPED_SOLIDUS: &str = r#"
{
"using": [ "urn:ietf:params:jmap:core" ],
"methodCalls": [
[ "Core\/echo", { "hello": true }, "c1" ]
]
}
"#;
#[test]
fn parse_request() {
println!("{:#?}", Request::parse(TEST.as_bytes(), 10, 10240));
println!("{:#?}", Request::parse(TEST1.as_bytes(), 10, 10240));
println!("{:#?}", Request::parse(TEST2.as_bytes(), 10, 10240));
}
#[test]
fn parse_method_name_with_escaped_solidus() {
let request = Request::parse(TEST_ESCAPED_SOLIDUS.as_bytes(), 10, 10240)
.expect("escaped solidus in method name must parse");
assert_eq!(request.method_calls.len(), 1);
assert!(matches!(
request.method_calls[0].method,
super::RequestMethod::Echo(_)
));
}
}