Import upstream v0.16.22, stripped

Upstream commit: 474dd0229cb20cf513036619781ed97bd8073c3f
Enterprise-only files removed or emptied: 63
Enterprise-only snippets removed: 117 in 50 files
Dangling module declarations removed: 5
Cargo edits turning enterprise off: 14
Verification: clean
Enterprise feature gates left for rebuilt features: 19 in 18 files

Produced by tools/fork/strip.py. The full report is in docs/fork/strip-reports/ on main.
This commit is contained in:
2026-09-18 10:21:56 -07:00
commit 7dae9b29fd
1650 changed files with 485521 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
[package]
name = "jmap_proto"
version = "0.16.22"
edition = "2024"
[dependencies]
utils = { path = "../utils" }
types = { path = "../types" }
trc = { path = "../trc" }
registry = { path = "../registry" }
jmap-tools = { version = "0.1" }
calcard = { version = "0.3" }
mail-parser = { version = "0.11", features = ["full_encoding", "rkyv"] }
serde = { version = "1.0", features = ["derive"]}
ahash = { version = "0.8.12", features = ["serde"] }
serde_json = { version = "1.0", features = ["raw_value"] }
hashify = "0.2"
rkyv = { version = "0.8.18", features = ["little_endian"] }
compact_str = { version = "0.10.0", features = ["rkyv", "serde"] }
[dev-dependencies]
[lints]
workspace = true
+182
View File
@@ -0,0 +1,182 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use serde::Serialize;
use serde::ser::SerializeMap;
use std::fmt::Display;
#[derive(Debug)]
pub enum MethodError {
InvalidArguments(String),
RequestTooLarge,
StateMismatch,
AnchorNotFound,
UnsupportedFilter(String),
UnsupportedSort(String),
ServerFail(String),
UnknownMethod(String),
ServerUnavailable,
ServerPartialFail,
InvalidResultReference(String),
Forbidden(String),
AccountNotFound,
AccountNotSupportedByMethod,
AccountReadOnly,
NotFound,
CannotCalculateChanges,
UnknownDataType,
}
#[derive(Debug)]
pub struct MethodErrorWrapper(trc::Error);
impl From<trc::Error> for MethodErrorWrapper {
fn from(value: trc::Error) -> Self {
MethodErrorWrapper(value)
}
}
impl Display for MethodError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
MethodError::InvalidArguments(err) => write!(f, "Invalid arguments: {}", err),
MethodError::RequestTooLarge => write!(f, "Request too large"),
MethodError::StateMismatch => write!(f, "State mismatch"),
MethodError::AnchorNotFound => write!(f, "Anchor not found"),
MethodError::UnsupportedFilter(err) => write!(f, "Unsupported filter: {}", err),
MethodError::UnsupportedSort(err) => write!(f, "Unsupported sort: {}", err),
MethodError::ServerFail(err) => write!(f, "Server error: {}", err),
MethodError::UnknownMethod(err) => write!(f, "Unknown method: {}", err),
MethodError::ServerUnavailable => write!(f, "Server unavailable"),
MethodError::ServerPartialFail => write!(f, "Server partial fail"),
MethodError::InvalidResultReference(err) => {
write!(f, "Invalid result reference: {}", err)
}
MethodError::Forbidden(err) => write!(f, "Forbidden: {}", err),
MethodError::AccountNotFound => write!(f, "Account not found"),
MethodError::AccountNotSupportedByMethod => {
write!(f, "Account not supported by method")
}
MethodError::AccountReadOnly => write!(f, "Account read only"),
MethodError::NotFound => write!(f, "Not found"),
MethodError::UnknownDataType => write!(f, "Unknown data type"),
MethodError::CannotCalculateChanges => write!(f, "Cannot calculate changes"),
}
}
}
impl Serialize for MethodErrorWrapper {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut map = serializer.serialize_map(2.into())?;
let description = self.0.value(trc::Key::Details).and_then(|v| v.as_str());
let (error_type, description) = match self.0.as_ref() {
trc::EventType::Jmap(cause) => match cause {
trc::JmapEvent::InvalidArguments => {
("invalidArguments", description.unwrap_or_default())
}
trc::JmapEvent::RequestTooLarge => (
"requestTooLarge",
concat!(
"The number of ids requested by the client exceeds the maximum number ",
"the server is willing to process in a single method call."
),
),
trc::JmapEvent::StateMismatch => (
"stateMismatch",
concat!(
"An \"ifInState\" argument was supplied, but ",
"it does not match the current state."
),
),
trc::JmapEvent::AnchorNotFound => (
"anchorNotFound",
concat!(
"An anchor argument was supplied, but it ",
"cannot be found in the results of the query."
),
),
trc::JmapEvent::UnsupportedFilter => {
("unsupportedFilter", description.unwrap_or_default())
}
trc::JmapEvent::UnsupportedSort => {
("unsupportedSort", description.unwrap_or_default())
}
trc::JmapEvent::NotFound => ("serverPartialFail", {
concat!(
"One or more items are no longer available on the ",
"server, please try again."
)
}),
trc::JmapEvent::UnknownMethod => ("unknownMethod", description.unwrap_or_default()),
trc::JmapEvent::InvalidResultReference => {
("invalidResultReference", description.unwrap_or_default())
}
trc::JmapEvent::Forbidden => ("forbidden", description.unwrap_or_default()),
trc::JmapEvent::AccountNotFound => (
"accountNotFound",
"The accountId does not correspond to a valid account",
),
trc::JmapEvent::AccountNotSupportedByMethod => (
"accountNotSupportedByMethod",
concat!(
"The accountId given corresponds to a valid account, ",
"but the account does not support this method or data type."
),
),
trc::JmapEvent::AccountReadOnly => (
"accountReadOnly",
"This method modifies state, but the account is read-only.",
),
trc::JmapEvent::UnknownDataType => (
"unknownDataType",
concat!(
"The server does not recognise this data type, ",
"or the capability to enable it is not present ",
"in the current Request Object."
),
),
trc::JmapEvent::CannotCalculateChanges => (
"cannotCalculateChanges",
concat!(
"The server cannot calculate the changes ",
"between the old and new states."
),
),
trc::JmapEvent::UnknownCapability
| trc::JmapEvent::NotJson
| trc::JmapEvent::NotRequest => (
"serverUnavailable",
concat!(
"This server is temporarily unavailable. ",
"Attempting this same operation later may succeed."
),
),
_ => (
"serverUnavailable",
"This server is temporarily unavailable.",
),
},
_ => (
"serverUnavailable",
concat!(
"This server is temporarily unavailable. ",
"Attempting this same operation later may succeed."
),
),
};
map.serialize_entry("type", error_type)?;
if !description.is_empty() {
map.serialize_entry("description", description)?;
}
map.end()
}
}
+9
View File
@@ -0,0 +1,9 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
pub mod method;
pub mod request;
pub mod set;
+425
View File
@@ -0,0 +1,425 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::{borrow::Cow, fmt::Display};
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
pub enum RequestLimitError {
#[serde(rename = "maxSizeRequest")]
SizeRequest,
#[serde(rename = "maxSizeUpload")]
SizeUpload,
#[serde(rename = "maxCallsInRequest")]
CallsIn,
#[serde(rename = "maxConcurrentRequests")]
ConcurrentRequest,
#[serde(rename = "maxConcurrentUpload")]
ConcurrentUpload,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub enum RequestErrorType {
#[serde(rename = "urn:ietf:params:jmap:error:unknownCapability")]
UnknownCapability,
#[serde(rename = "urn:ietf:params:jmap:error:notJSON")]
NotJSON,
#[serde(rename = "urn:ietf:params:jmap:error:notRequest")]
NotRequest,
#[serde(rename = "urn:ietf:params:jmap:error:limit")]
Limit,
#[serde(rename = "about:blank")]
Other,
}
#[derive(Debug, Clone)]
pub struct RateLimitPolicy {
pub name: &'static str,
pub limit: u64,
pub remaining: u64,
pub window: Option<u64>,
pub reset: Option<u64>,
pub unit: RateLimitUnit,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RateLimitUnit {
Requests,
ContentBytes,
ConcurrentRequests,
}
impl RateLimitUnit {
pub fn as_str(self) -> &'static str {
match self {
RateLimitUnit::Requests => "requests",
RateLimitUnit::ContentBytes => "content-bytes",
RateLimitUnit::ConcurrentRequests => "concurrent-requests",
}
}
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct RequestError<'x> {
#[serde(rename = "type")]
pub p_type: RequestErrorType,
pub status: u16,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<Cow<'x, str>>,
pub detail: Cow<'x, str>,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<RequestLimitError>,
#[serde(skip)]
pub rate_limit: Vec<RateLimitPolicy>,
#[serde(skip)]
pub retry_after: Option<u64>,
}
impl<'x> RequestError<'x> {
pub fn blank(
status: u16,
title: impl Into<Cow<'x, str>>,
detail: impl Into<Cow<'x, str>>,
) -> Self {
RequestError {
p_type: RequestErrorType::Other,
status,
title: Some(title.into()),
detail: detail.into(),
limit: None,
rate_limit: Vec::new(),
retry_after: None,
}
}
pub fn with_rate_limit(mut self, policy: RateLimitPolicy) -> Self {
if let Some(reset) = policy.reset
&& self.retry_after.is_none_or(|r| reset > r)
{
self.retry_after = Some(reset);
}
self.rate_limit.push(policy);
self
}
pub fn with_retry_after(mut self, seconds: u64) -> Self {
if self.retry_after.is_none_or(|r| seconds > r) {
self.retry_after = Some(seconds);
}
self
}
pub fn internal_server_error() -> Self {
RequestError::blank(
500,
"Internal Server Error",
concat!(
"There was a problem while processing your request. ",
"Please contact the system administrator if this problem persists."
),
)
}
pub fn unavailable() -> Self {
RequestError::blank(
503,
"Temporarily Unavailable",
concat!(
"There was a temporary problem while processing your request. ",
"Please try again in a few moments."
),
)
}
pub fn invalid_parameters() -> Self {
RequestError::blank(
400,
"Invalid Parameters",
"One or multiple parameters could not be parsed.",
)
}
pub fn forbidden() -> Self {
RequestError::blank(
403,
"Forbidden",
"You do not have enough permissions to access this resource.",
)
}
pub fn over_blob_quota(max_files: usize, max_bytes: usize) -> Self {
RequestError::blank(
429,
"Quota exceeded",
format!(
"You have exceeded the blob upload quota of {} files or {} bytes.",
max_files, max_bytes
),
)
}
pub fn over_quota() -> Self {
RequestError::blank(
403,
"Quota exceeded",
"You have exceeded your account quota.",
)
}
pub fn tenant_over_quota() -> Self {
RequestError::blank(
403,
"Tenant quota exceeded",
"Your organization has exceeded its quota.",
)
}
pub fn too_many_requests() -> Self {
RequestError::blank(
429,
"Too Many Requests",
"Your request has been rate limited. Please try again in a few seconds.",
)
}
pub fn too_many_auth_attempts() -> Self {
RequestError::blank(
429,
"Too Many Authentication Attempts",
"Your request has been rate limited. Please try again in a few minutes.",
)
}
pub fn limit(limit_type: RequestLimitError) -> Self {
RequestError {
p_type: RequestErrorType::Limit,
status: 400,
title: None,
detail: match limit_type {
RequestLimitError::SizeRequest => concat!(
"The request is larger than the server ",
"is willing to process."
),
RequestLimitError::SizeUpload => concat!(
"The uploaded file is larger than the server ",
"is willing to process."
),
RequestLimitError::CallsIn => concat!(
"The request exceeds the maximum number ",
"of calls in a single request."
),
RequestLimitError::ConcurrentRequest => concat!(
"The request exceeds the maximum number ",
"of concurrent requests."
),
RequestLimitError::ConcurrentUpload => concat!(
"The request exceeds the maximum number ",
"of concurrent uploads."
),
}
.into(),
limit: Some(limit_type),
rate_limit: Vec::new(),
retry_after: None,
}
}
pub fn not_found() -> Self {
RequestError::blank(
404,
"Not Found",
"The requested resource does not exist on this server.",
)
}
pub fn unauthorized() -> Self {
RequestError::blank(401, "Unauthorized", "You have to authenticate first.")
}
pub fn unknown_capability(capability: &'_ str) -> RequestError<'_> {
RequestError {
p_type: RequestErrorType::UnknownCapability,
limit: None,
title: None,
status: 400,
detail: format!(
concat!(
"The Request object used capability ",
"'{}', which is not supported",
"by this server."
),
capability
)
.into(),
rate_limit: Vec::new(),
retry_after: None,
}
}
pub fn not_json(detail: &'_ str) -> RequestError<'_> {
RequestError {
p_type: RequestErrorType::NotJSON,
limit: None,
title: None,
status: 400,
detail: format!("Failed to parse JSON: {detail}").into(),
rate_limit: Vec::new(),
retry_after: None,
}
}
pub fn not_request(detail: impl Into<Cow<'x, str>>) -> RequestError<'x> {
RequestError {
p_type: RequestErrorType::NotRequest,
limit: None,
title: None,
status: 400,
detail: detail.into(),
rate_limit: Vec::new(),
retry_after: None,
}
}
}
impl RateLimitPolicy {
pub fn new(name: &'static str, limit: u64) -> Self {
RateLimitPolicy {
name,
limit,
remaining: 0,
window: None,
reset: None,
unit: RateLimitUnit::Requests,
}
}
pub fn with_window(mut self, window: u64) -> Self {
self.window = Some(window);
self
}
pub fn with_reset(mut self, reset: u64) -> Self {
self.reset = Some(reset);
self
}
pub fn with_remaining(mut self, remaining: u64) -> Self {
self.remaining = remaining;
self
}
pub fn with_unit(mut self, unit: RateLimitUnit) -> Self {
self.unit = unit;
self
}
pub fn fmt_policy(&self, out: &mut String) {
use std::fmt::Write;
let _ = write!(out, "\"{}\";q={}", self.name, self.limit);
if let Some(window) = self.window {
let _ = write!(out, ";w={window}");
}
if !matches!(self.unit, RateLimitUnit::Requests) {
let _ = write!(out, ";qu=\"{}\"", self.unit.as_str());
}
}
pub fn fmt_state(&self, out: &mut String) {
use std::fmt::Write;
let _ = write!(out, "\"{}\";r={}", self.name, self.remaining);
if let Some(reset) = self.reset {
let _ = write!(out, ";t={reset}");
}
}
}
impl<'x> RequestError<'x> {
pub fn rate_limit_policy_header(&self) -> Option<String> {
if self.rate_limit.is_empty() {
return None;
}
let mut out = String::new();
for (i, policy) in self.rate_limit.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
policy.fmt_policy(&mut out);
}
Some(out)
}
pub fn rate_limit_state_header(&self) -> Option<String> {
if self.rate_limit.is_empty() {
return None;
}
let mut out = String::new();
for (i, policy) in self.rate_limit.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
policy.fmt_state(&mut out);
}
Some(out)
}
}
impl Display for RequestError<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.detail)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rate_limit_headers_match_spec() {
// Spec example: RateLimit-Policy: "burst";q=100;w=60,"daily";q=1000;w=86400
let mut p1 = String::new();
RateLimitPolicy::new("burst", 100)
.with_window(60)
.fmt_policy(&mut p1);
assert_eq!(p1, r#""burst";q=100;w=60"#);
// Spec example: RateLimit-Policy: "peruser";q=65535;qu="content-bytes";w=10
let mut p2 = String::new();
RateLimitPolicy::new("peruser", 65535)
.with_window(10)
.with_unit(RateLimitUnit::ContentBytes)
.fmt_policy(&mut p2);
assert_eq!(p2, r#""peruser";q=65535;w=10;qu="content-bytes""#);
// Spec example: RateLimit: "default";r=50;t=30
let mut s = String::new();
RateLimitPolicy::new("default", 100)
.with_remaining(50)
.with_reset(30)
.fmt_state(&mut s);
assert_eq!(s, r#""default";r=50;t=30"#);
// Two policies in one header
let err = RequestError::too_many_requests()
.with_rate_limit(
RateLimitPolicy::new("burst", 100)
.with_window(60)
.with_reset(30),
)
.with_rate_limit(
RateLimitPolicy::new("daily", 1000)
.with_window(86400)
.with_reset(3600),
);
assert_eq!(
err.rate_limit_policy_header().as_deref(),
Some(r#""burst";q=100;w=60, "daily";q=1000;w=86400"#),
);
assert_eq!(
err.rate_limit_state_header().as_deref(),
Some(r#""burst";r=0;t=30, "daily";r=0;t=3600"#),
);
assert_eq!(err.retry_after, Some(3600));
}
}
+352
View File
@@ -0,0 +1,352 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use jmap_tools::{Key, Property};
use registry::types::{
error::{PatchError, ValidationError},
id::ObjectId,
};
use std::borrow::Cow;
use types::id::Id;
#[derive(Debug, Clone, serde::Serialize)]
#[serde(bound(serialize = "InvalidProperty<P>: serde::Serialize"))]
#[serde(transparent)]
#[repr(transparent)]
pub struct SetError<P: Property>(Box<SetErrorInner<P>>);
#[derive(Debug, Clone, serde::Serialize)]
#[serde(bound(serialize = "InvalidProperty<P>: serde::Serialize"))]
struct SetErrorInner<P: Property> {
#[serde(rename = "type")]
type_: SetErrorType,
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<Cow<'static, str>>,
#[serde(skip_serializing_if = "Option::is_none")]
properties: Option<Vec<InvalidProperty<P>>>,
#[serde(rename = "existingId")]
#[serde(skip_serializing_if = "Option::is_none")]
existing_id: Option<Id>,
#[serde(rename = "objectId")]
#[serde(skip_serializing_if = "Option::is_none")]
object_id: Option<ObjectId>,
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(rename = "linkedObjects")]
linked_objects: Vec<ObjectId>,
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(rename = "validationErrors")]
validation_errors: Vec<ValidationError>,
}
#[derive(Debug, Clone)]
pub enum InvalidProperty<T: Property> {
Property(Key<'static, T>),
Path(Vec<Key<'static, T>>),
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum SetErrorType {
#[serde(rename = "forbidden")]
Forbidden,
#[serde(rename = "overQuota")]
OverQuota,
#[serde(rename = "tooLarge")]
TooLarge,
#[serde(rename = "rateLimit")]
RateLimit,
#[serde(rename = "notFound")]
NotFound,
#[serde(rename = "invalidPatch")]
InvalidPatch,
#[serde(rename = "willDestroy")]
WillDestroy,
#[serde(rename = "invalidProperties")]
InvalidProperties,
#[serde(rename = "singleton")]
Singleton,
#[serde(rename = "mailboxHasChild")]
MailboxHasChild,
#[serde(rename = "mailboxHasEmail")]
MailboxHasEmail,
#[serde(rename = "blobNotFound")]
BlobNotFound,
#[serde(rename = "tooManyKeywords")]
TooManyKeywords,
#[serde(rename = "tooManyMailboxes")]
TooManyMailboxes,
#[serde(rename = "forbiddenFrom")]
ForbiddenFrom,
#[serde(rename = "invalidEmail")]
InvalidEmail,
#[serde(rename = "tooManyRecipients")]
TooManyRecipients,
#[serde(rename = "noRecipients")]
NoRecipients,
#[serde(rename = "invalidRecipients")]
InvalidRecipients,
#[serde(rename = "forbiddenMailFrom")]
ForbiddenMailFrom,
#[serde(rename = "forbiddenToSend")]
ForbiddenToSend,
#[serde(rename = "cannotUnsend")]
CannotUnsend,
#[serde(rename = "alreadyExists")]
AlreadyExists,
#[serde(rename = "invalidScript")]
InvalidScript,
#[serde(rename = "scriptIsActive")]
ScriptIsActive,
#[serde(rename = "addressBookHasContents")]
AddressBookHasContents,
#[serde(rename = "nodeHasChildren")]
NodeHasChildren,
#[serde(rename = "calendarHasEvent")]
CalendarHasEvent,
#[serde(rename = "noSupportedScheduleMethods")]
NoSupportedScheduleMethods,
// Stalwart registry errors
#[serde(rename = "objectIsLinked")]
ObjectIsLinked,
#[serde(rename = "invalidForeignKey")]
InvalidForeignKey,
#[serde(rename = "primaryKeyViolation")]
PrimaryKeyViolation,
#[serde(rename = "validationFailed")]
ValidationFailed,
}
impl SetErrorType {
pub fn as_str(&self) -> &'static str {
match self {
SetErrorType::Forbidden => "forbidden",
SetErrorType::OverQuota => "overQuota",
SetErrorType::TooLarge => "tooLarge",
SetErrorType::RateLimit => "rateLimit",
SetErrorType::NotFound => "notFound",
SetErrorType::InvalidPatch => "invalidPatch",
SetErrorType::WillDestroy => "willDestroy",
SetErrorType::InvalidProperties => "invalidProperties",
SetErrorType::Singleton => "singleton",
SetErrorType::BlobNotFound => "blobNotFound",
SetErrorType::MailboxHasChild => "mailboxHasChild",
SetErrorType::MailboxHasEmail => "mailboxHasEmail",
SetErrorType::TooManyKeywords => "tooManyKeywords",
SetErrorType::TooManyMailboxes => "tooManyMailboxes",
SetErrorType::ForbiddenFrom => "forbiddenFrom",
SetErrorType::InvalidEmail => "invalidEmail",
SetErrorType::TooManyRecipients => "tooManyRecipients",
SetErrorType::NoRecipients => "noRecipients",
SetErrorType::InvalidRecipients => "invalidRecipients",
SetErrorType::ForbiddenMailFrom => "forbiddenMailFrom",
SetErrorType::ForbiddenToSend => "forbiddenToSend",
SetErrorType::CannotUnsend => "cannotUnsend",
SetErrorType::AlreadyExists => "alreadyExists",
SetErrorType::InvalidScript => "invalidScript",
SetErrorType::ScriptIsActive => "scriptIsActive",
SetErrorType::AddressBookHasContents => "addressBookHasContents",
SetErrorType::NodeHasChildren => "nodeHasChildren",
SetErrorType::CalendarHasEvent => "calendarHasEvent",
SetErrorType::NoSupportedScheduleMethods => "noSupportedScheduleMethods",
SetErrorType::ObjectIsLinked => "objectIsLinked",
SetErrorType::InvalidForeignKey => "invalidForeignKey",
SetErrorType::PrimaryKeyViolation => "primaryKeyViolation",
SetErrorType::ValidationFailed => "validationFailed",
}
}
}
impl<T: Property> SetError<T> {
pub fn new(type_: SetErrorType) -> Self {
SetError(Box::new(SetErrorInner {
type_,
description: None,
properties: None,
existing_id: None,
object_id: None,
linked_objects: Vec::new(),
validation_errors: Vec::new(),
}))
}
pub fn with_description(mut self, description: impl Into<Cow<'static, str>>) -> Self {
self.0.description = description.into().into();
self
}
pub fn error_type(&self) -> &SetErrorType {
&self.0.type_
}
pub fn description(&self) -> Option<&str> {
self.0.description.as_deref()
}
pub fn validation_errors(&self) -> &[ValidationError] {
&self.0.validation_errors
}
pub fn with_property(mut self, property: impl Into<InvalidProperty<T>>) -> Self {
self.0.properties = vec![property.into()].into();
self
}
pub fn with_properties(
mut self,
properties: impl IntoIterator<Item = impl Into<InvalidProperty<T>>>,
) -> Self {
self.0.properties = properties
.into_iter()
.map(Into::into)
.collect::<Vec<_>>()
.into();
self
}
pub fn with_object_id(mut self, object_id: ObjectId) -> Self {
self.0.object_id = object_id.into();
self
}
pub fn with_object_id_opt(mut self, object_id: Option<ObjectId>) -> Self {
self.0.object_id = object_id;
self
}
pub fn with_linked_objects(mut self, linked_objects: Vec<ObjectId>) -> Self {
self.0.linked_objects = linked_objects;
self
}
pub fn with_validation_errors(mut self, validation_errors: Vec<ValidationError>) -> Self {
self.0.validation_errors = validation_errors;
self
}
pub fn with_existing_id(mut self, id: Id) -> Self {
self.0.existing_id = id.into();
self
}
pub fn invalid_properties() -> Self {
Self::new(SetErrorType::InvalidProperties)
}
pub fn invalid_patch() -> Self {
Self::new(SetErrorType::InvalidPatch)
}
pub fn forbidden() -> Self {
Self::new(SetErrorType::Forbidden)
}
pub fn not_found() -> Self {
Self::new(SetErrorType::NotFound)
}
pub fn blob_not_found() -> Self {
Self::new(SetErrorType::BlobNotFound)
}
pub fn over_quota() -> Self {
Self::new(SetErrorType::OverQuota).with_description("Account quota exceeded.")
}
pub fn already_exists() -> Self {
Self::new(SetErrorType::AlreadyExists)
}
pub fn no_supported_schedule_methods(calendar_address: &str) -> Self {
Self::new(SetErrorType::NoSupportedScheduleMethods).with_description(format!(
"No supported scheduling method for calendar address {calendar_address}."
))
}
pub fn too_large() -> Self {
Self::new(SetErrorType::TooLarge)
}
pub fn will_destroy() -> Self {
Self::new(SetErrorType::WillDestroy).with_description("ID will be destroyed.")
}
pub fn singleton() -> Self {
Self::new(SetErrorType::Singleton)
.with_description("Singletons cannot be created or destroyed.")
}
pub fn address_book_has_contents() -> Self {
Self::new(SetErrorType::AddressBookHasContents)
.with_description("Address book is not empty.")
}
pub fn node_has_children() -> Self {
Self::new(SetErrorType::NodeHasChildren).with_description("Cannot delete non-empty folder.")
}
pub fn calendar_has_event() -> Self {
Self::new(SetErrorType::CalendarHasEvent).with_description("Calendar is not empty.")
}
}
impl<T: Property> From<T> for InvalidProperty<T> {
fn from(property: T) -> Self {
InvalidProperty::Property(Key::Property(property))
}
}
impl<T: Property> From<(T, T)> for InvalidProperty<T> {
fn from((a, b): (T, T)) -> Self {
InvalidProperty::Path(vec![Key::Property(a), Key::Property(b)])
}
}
impl<T: Property> From<Key<'static, T>> for InvalidProperty<T> {
fn from(property: Key<'static, T>) -> Self {
InvalidProperty::Property(property)
}
}
impl<T: Property> serde::Serialize for InvalidProperty<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
InvalidProperty::Property(p) => p.serialize(serializer),
InvalidProperty::Path(p) => {
use std::fmt::Write;
let mut path = String::with_capacity(64);
for (i, p) in p.iter().enumerate() {
if i > 0 {
path.push('/');
}
let _ = write!(path, "{}", p.to_string());
}
path.serialize(serializer)
}
}
}
}
impl From<PatchError> for SetError<registry::schema::properties::Property> {
fn from(err: PatchError) -> Self {
SetError(Box::new(SetErrorInner {
type_: SetErrorType::InvalidPatch,
description: err.message.into(),
properties: Some(vec![InvalidProperty::Property(Key::Owned(err.path))]),
existing_id: None,
object_id: None,
linked_objects: Vec::new(),
validation_errors: Vec::new(),
}))
}
}
+13
View File
@@ -0,0 +1,13 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
pub mod error;
pub mod method;
pub mod object;
pub mod references;
pub mod request;
pub mod response;
pub mod types;
@@ -0,0 +1,91 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
request::{
MaybeInvalid,
deserialize::{DeserializeArguments, deserialize_request},
},
types::date::UTCDate,
};
use calcard::jscalendar::{JSCalendar, JSCalendarProperty};
use serde::{Deserialize, Deserializer, Serialize};
use types::{blob::BlobId, id::Id};
#[derive(Debug, Clone, Default)]
pub struct GetAvailabilityRequest {
pub account_id: Id,
pub id: Id,
pub utc_start: UTCDate,
pub utc_end: UTCDate,
pub show_details: bool,
pub event_properties: Option<Vec<MaybeInvalid<JSCalendarProperty<Id>>>>,
}
#[derive(Debug, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct GetAvailabilityResponse {
pub list: Vec<BusyPeriod>,
}
#[derive(Debug, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct BusyPeriod {
pub utc_start: UTCDate,
pub utc_end: UTCDate,
pub busy_status: Option<BusyStatus>,
pub event: Option<JSCalendar<'static, Id, BlobId>>,
}
#[derive(Debug, Serialize, Clone, Copy, PartialOrd, Ord, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum BusyStatus {
Confirmed,
Tentative,
Unavailable,
}
impl<'de> DeserializeArguments<'de> for GetAvailabilityRequest {
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"accountId" => {
self.account_id = crate::request::deserialize_account_id(map)?;
},
b"utcStart" => {
self.utc_start = map.next_value()?;
},
b"utcEnd" => {
self.utc_end = map.next_value()?;
},
b"id" => {
self.id = map.next_value()?;
},
b"showDetails" => {
self.show_details = map.next_value()?;
},
b"eventProperties" => {
self.event_properties = map.next_value()?;
},
_ => {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
);
Ok(())
}
}
impl<'de> Deserialize<'de> for GetAvailabilityRequest {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserialize_request(deserializer)
}
}
+85
View File
@@ -0,0 +1,85 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
method::PropertyWrapper,
object::JmapObject,
request::deserialize::{DeserializeArguments, deserialize_request},
types::state::State,
};
use serde::{Deserialize, Deserializer};
use types::id::Id;
#[derive(Debug, Clone, Default)]
pub struct ChangesRequest {
pub account_id: Id,
pub since_state: State,
pub max_changes: Option<usize>,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct ChangesResponse<T: JmapObject> {
#[serde(rename = "accountId")]
pub account_id: Id,
#[serde(rename = "oldState")]
pub old_state: State,
#[serde(rename = "newState")]
pub new_state: State,
#[serde(rename = "hasMoreChanges")]
pub has_more_changes: bool,
pub created: Vec<Id>,
pub updated: Vec<Id>,
pub destroyed: Vec<Id>,
#[serde(rename = "updatedProperties")]
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_properties: Option<Vec<PropertyWrapper<T::Property>>>,
}
impl<'de> DeserializeArguments<'de> for ChangesRequest {
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"accountId" => {
self.account_id = crate::request::deserialize_account_id(map)?;
},
b"sinceState" => {
self.since_state = map.next_value()?;
},
b"maxChanges" => {
self.max_changes = map.next_value()?;
},
_ => {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
);
Ok(())
}
}
impl<'de> Deserialize<'de> for ChangesRequest {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserialize_request(deserializer)
}
}
impl<T: JmapObject> ChangesResponse<T> {
pub fn has_changes(&self) -> bool {
!self.created.is_empty() || !self.updated.is_empty() || !self.destroyed.is_empty()
}
}
+185
View File
@@ -0,0 +1,185 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
error::set::SetError,
object::{JmapObject, blob::BlobProperty},
request::{
MaybeInvalid,
deserialize::{DeserializeArguments, deserialize_request},
reference::MaybeIdReference,
},
types::state::State,
};
use jmap_tools::{Key, Map, Value};
use serde::{Deserialize, Deserializer, Serialize};
use types::{blob::BlobId, id::Id};
use utils::map::vec_map::VecMap;
#[derive(Debug, Clone)]
pub struct CopyRequest<'x, T: JmapObject> {
pub from_account_id: Id,
pub if_from_in_state: Option<State>,
pub account_id: Id,
pub if_in_state: Option<State>,
pub create: VecMap<MaybeIdReference<Id>, Value<'x, T::Property, T::Element>>,
pub on_success_destroy_original: Option<bool>,
pub destroy_from_if_in_state: Option<State>,
pub arguments: T::CopyArguments,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct CopyResponse<T: JmapObject> {
#[serde(rename = "fromAccountId")]
pub from_account_id: Id,
#[serde(rename = "accountId")]
pub account_id: Id,
#[serde(rename = "oldState")]
pub old_state: State,
#[serde(rename = "newState")]
pub new_state: State,
#[serde(rename = "created")]
#[serde(skip_serializing_if = "VecMap::is_empty")]
pub created: VecMap<Id, Value<'static, T::Property, T::Element>>,
#[serde(rename = "notCreated")]
#[serde(skip_serializing_if = "VecMap::is_empty")]
pub not_created: VecMap<Id, SetError<T::Property>>,
}
#[derive(Debug, Clone, Default)]
pub struct CopyBlobRequest {
pub from_account_id: Id,
pub account_id: Id,
pub blob_ids: Vec<MaybeInvalid<BlobId>>,
}
#[derive(Debug, Clone, Serialize)]
pub struct CopyBlobResponse {
#[serde(rename = "fromAccountId")]
pub from_account_id: Id,
#[serde(rename = "accountId")]
pub account_id: Id,
#[serde(rename = "copied")]
#[serde(skip_serializing_if = "VecMap::is_empty")]
pub copied: VecMap<BlobId, BlobId>,
#[serde(rename = "notCopied")]
#[serde(skip_serializing_if = "VecMap::is_empty")]
pub not_copied: VecMap<MaybeInvalid<BlobId>, SetError<BlobProperty>>,
}
impl<'de, T: JmapObject> DeserializeArguments<'de> for CopyRequest<'de, T> {
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"accountId" => {
self.account_id = crate::request::deserialize_account_id(map)?;
},
b"ifInState" => {
self.if_in_state = map.next_value()?;
},
b"fromAccountId" => {
self.from_account_id = crate::request::deserialize_account_id(map)?;
},
b"ifFromInState" => {
self.if_from_in_state = map.next_value()?;
},
b"create" => {
self.create = map.next_value()?;
},
b"onSuccessDestroyOriginal" => {
self.on_success_destroy_original = map.next_value()?;
},
b"destroyFromIfInState" => {
self.destroy_from_if_in_state = map.next_value()?;
},
_ => {
self.arguments.deserialize_argument(key, map)?;
}
);
Ok(())
}
}
impl<'de> DeserializeArguments<'de> for CopyBlobRequest {
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"accountId" => {
self.account_id = crate::request::deserialize_account_id(map)?;
},
b"fromAccountId" => {
self.from_account_id = crate::request::deserialize_account_id(map)?;
},
b"blobIds" => {
self.blob_ids = map.next_value()?;
},
_ => {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
);
Ok(())
}
}
impl<'de, T: JmapObject> Deserialize<'de> for CopyRequest<'de, T> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserialize_request(deserializer)
}
}
impl<'de> Deserialize<'de> for CopyBlobRequest {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserialize_request(deserializer)
}
}
impl<'de, T: JmapObject> Default for CopyRequest<'de, T> {
fn default() -> Self {
CopyRequest {
from_account_id: Id::default(),
if_from_in_state: None,
account_id: Id::default(),
if_in_state: None,
create: VecMap::new(),
on_success_destroy_original: None,
destroy_from_if_in_state: None,
arguments: T::CopyArguments::default(),
}
}
}
impl<T: JmapObject> CopyResponse<T> {
pub fn created(&mut self, id: Id, document_id: impl Into<T::Id>) {
let document_id = document_id.into();
self.created.append(
id,
Value::Object(Map::from(vec![(
Key::Property(T::ID_PROPERTY),
Value::Element(document_id.into()),
)])),
);
}
}
+151
View File
@@ -0,0 +1,151 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
object::JmapObject,
request::{
MaybeInvalid,
deserialize::{DeserializeArguments, deserialize_request},
reference::{MaybeIdReference, MaybeResultReference, ResultReference},
},
types::state::State,
};
use jmap_tools::Value;
use serde::{Deserialize, Deserializer};
use types::id::Id;
#[derive(Debug, Clone)]
pub struct GetRequest<T: JmapObject> {
pub account_id: Id,
pub ids: Option<MaybeResultReference<Vec<MaybeIdReference<T::Id>>>>,
pub properties: Option<MaybeResultReference<Vec<MaybeInvalid<T::Property>>>>,
pub arguments: T::GetArguments,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct GetResponse<T: JmapObject> {
#[serde(rename = "accountId")]
#[serde(skip_serializing_if = "Option::is_none")]
pub account_id: Option<Id>,
#[serde(skip_serializing_if = "Option::is_none")]
pub state: Option<State>,
pub list: Vec<Value<'static, T::Property, T::Element>>,
#[serde(rename = "notFound")]
pub not_found: Vec<MaybeInvalid<T::Id>>,
}
impl<T: JmapObject> GetResponse<T> {
pub fn push_not_found(&mut self, id: T::Id) {
self.not_found.push(MaybeInvalid::Value(id));
}
}
impl<'de, T: JmapObject> DeserializeArguments<'de> for GetRequest<T> {
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"accountId" => {
self.account_id = crate::request::deserialize_account_id(map)?;
},
b"ids" => {
self.ids = map.next_value::<Option<Vec<MaybeIdReference<T::Id>>>>()?.map(MaybeResultReference::Value);
},
b"properties" => {
self.properties = map.next_value::<Option<Vec<MaybeInvalid<T::Property>>>>()?.map(MaybeResultReference::Value);
},
b"#ids" => {
self.ids = Some(MaybeResultReference::Reference(map.next_value::<ResultReference>()?));
},
b"#properties" => {
self.properties = Some(MaybeResultReference::Reference(map.next_value::<ResultReference>()?));
},
_ => {
self.arguments.deserialize_argument(key, map)?;
}
);
Ok(())
}
}
impl<'de, T: JmapObject> Deserialize<'de> for GetRequest<T> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserialize_request(deserializer)
}
}
impl<T: JmapObject> Default for GetRequest<T> {
fn default() -> Self {
Self {
account_id: Id::default(),
ids: None,
properties: None,
arguments: T::GetArguments::default(),
}
}
}
impl<T: JmapObject> GetRequest<T> {
pub fn unwrap_properties(&mut self, default: &[T::Property]) -> Vec<T::Property> {
if let Some(properties_) = self.properties.take().map(|p| p.unwrap()) {
let mut properties = Vec::with_capacity(properties_.len());
let id_prop = T::ID_PROPERTY;
let mut has_id = false;
for prop in properties_ {
if let MaybeInvalid::Value(p) = prop {
if p == id_prop {
has_id = true;
}
properties.push(p);
}
}
if !has_id {
properties.push(id_prop);
}
properties
} else {
default.to_vec()
}
}
#[allow(clippy::type_complexity)]
pub fn unwrap_ids(
&mut self,
max_objects_in_get: usize,
) -> trc::Result<(Option<Vec<T::Id>>, Vec<MaybeInvalid<T::Id>>)> {
if let Some(ids) = self.ids.take() {
let ids = ids.unwrap();
if ids.len() <= max_objects_in_get {
let mut valid = Vec::with_capacity(ids.len());
let mut invalid = Vec::new();
for id in ids {
match id {
MaybeIdReference::Id(id) => valid.push(id),
MaybeIdReference::Invalid(s) | MaybeIdReference::Reference(s) => {
invalid.push(MaybeInvalid::Invalid(s))
}
}
}
Ok((Some(valid), invalid))
} else {
Err(trc::JmapEvent::RequestTooLarge.into_err())
}
} else {
Ok((None, Vec::new()))
}
}
}
+146
View File
@@ -0,0 +1,146 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
error::set::SetError,
method::JmapDict,
object::{
AnyId,
email::{EmailProperty, EmailValue},
},
request::{
MaybeInvalid,
deserialize::{DeserializeArguments, deserialize_request},
reference::{MaybeIdReference, MaybeResultReference, ResultReference},
},
response::Response,
types::{date::UTCDate, state::State},
};
use jmap_tools::{Key, Value};
use serde::{Deserialize, Deserializer};
use types::{blob::BlobId, id::Id, keyword::Keyword};
use utils::map::vec_map::VecMap;
#[derive(Debug, Clone, Default)]
pub struct ImportEmailRequest {
pub account_id: Id,
pub if_in_state: Option<State>,
pub emails: VecMap<String, ImportEmail>,
}
#[derive(Debug, Clone, Default)]
pub struct ImportEmail {
pub blob_id: MaybeInvalid<BlobId>,
pub mailbox_ids: MaybeResultReference<Vec<MaybeIdReference<Id>>>,
pub keywords: Vec<Keyword>,
pub received_at: Option<UTCDate>,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct ImportEmailResponse {
#[serde(rename = "accountId")]
pub account_id: Id,
#[serde(rename = "oldState")]
#[serde(skip_serializing_if = "Option::is_none")]
pub old_state: Option<State>,
#[serde(rename = "newState")]
pub new_state: State,
#[serde(rename = "created")]
#[serde(skip_serializing_if = "VecMap::is_empty")]
pub created: VecMap<String, Value<'static, EmailProperty, EmailValue>>,
#[serde(rename = "notCreated")]
#[serde(skip_serializing_if = "VecMap::is_empty")]
pub not_created: VecMap<String, SetError<EmailProperty>>,
}
impl<'de> DeserializeArguments<'de> for ImportEmailRequest {
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"accountId" => {
self.account_id = crate::request::deserialize_account_id(map)?;
},
b"ifInState" => {
self.if_in_state = map.next_value()?;
},
b"emails" => {
self.emails = map.next_value()?;
}
_ => {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
);
Ok(())
}
}
impl<'de> DeserializeArguments<'de> for ImportEmail {
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"blobId" => {
self.blob_id = map.next_value()?;
},
b"keywords" => {
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::<JmapDict<MaybeIdReference<Id>>>()?.0);
},
b"#mailboxIds" => {
self.mailbox_ids = MaybeResultReference::Reference(map.next_value::<ResultReference>()?);
},
_ => {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
);
Ok(())
}
}
impl<'de> Deserialize<'de> for ImportEmail {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserialize_request(deserializer)
}
}
impl<'de> Deserialize<'de> for ImportEmailRequest {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserialize_request(deserializer)
}
}
impl ImportEmailResponse {
pub fn update_created_ids(&self, response: &mut Response) {
for (user_id, obj) in &self.created {
if let Value::Object(obj) = obj
&& let Some(Value::Element(EmailValue::Id(id))) =
obj.get(&Key::Property(EmailProperty::Id))
{
response.created_ids.insert(user_id.clone(), AnyId::Id(*id));
}
}
}
}
+72
View File
@@ -0,0 +1,72 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::request::{
MaybeInvalid,
deserialize::{DeserializeArguments, deserialize_request},
};
use serde::{Deserialize, Deserializer};
use types::{blob::BlobId, id::Id, type_state::DataType};
use utils::map::vec_map::VecMap;
#[derive(Debug, Clone, Default)]
pub struct BlobLookupRequest {
pub account_id: Id,
pub type_names: Vec<MaybeInvalid<DataType>>,
pub ids: Vec<MaybeInvalid<BlobId>>,
}
#[derive(Debug, Clone, Default, serde::Serialize)]
pub struct BlobLookupResponse {
#[serde(rename = "accountId")]
pub account_id: Id,
#[serde(rename = "list")]
pub list: Vec<BlobInfo>,
#[serde(rename = "notFound")]
pub not_found: Vec<BlobId>,
}
#[derive(Debug, Clone, Default, serde::Serialize)]
pub struct BlobInfo {
pub id: BlobId,
#[serde(rename = "matchedIds")]
pub matched_ids: VecMap<DataType, Vec<Id>>,
}
impl<'de> DeserializeArguments<'de> for BlobLookupRequest {
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"accountId" => {
self.account_id = crate::request::deserialize_account_id(map)?;
},
b"typeNames" => {
self.type_names = map.next_value()?;
},
b"ids" => {
self.ids = map.next_value()?;
},
_ => {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
);
Ok(())
}
}
impl<'de> Deserialize<'de> for BlobLookupRequest {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserialize_request(deserializer)
}
}
+92
View File
@@ -0,0 +1,92 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use ahash::AHashMap;
use jmap_tools::Property;
use serde::{
Deserialize, Deserializer, Serialize, Serializer,
de::{self, MapAccess, Visitor},
};
use std::{borrow::Cow, fmt, str::FromStr};
pub mod availability;
pub mod changes;
pub mod copy;
pub mod get;
pub mod import;
pub mod lookup;
pub mod parse;
pub mod query;
pub mod query_changes;
pub mod search_snippet;
pub mod set;
pub mod upload;
pub mod validate;
#[inline(always)]
fn ahash_is_empty<K, V>(map: &AHashMap<K, V>) -> bool {
map.is_empty()
}
#[derive(Debug, Clone)]
#[repr(transparent)]
pub struct PropertyWrapper<T: Property>(pub T);
impl<T: Property> From<T> for PropertyWrapper<T> {
fn from(value: T) -> Self {
Self(value)
}
}
impl<T: Property> Serialize for PropertyWrapper<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.0.to_cow().as_ref())
}
}
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,
})
}
}
+88
View File
@@ -0,0 +1,88 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
object::JmapObject,
request::{
MaybeInvalid,
deserialize::{DeserializeArguments, deserialize_request},
reference::MaybeIdReference,
},
};
use jmap_tools::Value;
use serde::{Deserialize, Deserializer};
use types::{blob::BlobId, id::Id};
use utils::map::vec_map::VecMap;
#[derive(Debug, Clone)]
pub struct ParseRequest<T: JmapObject> {
pub account_id: Id,
pub blob_ids: Vec<MaybeIdReference<BlobId>>,
pub properties: Option<Vec<MaybeInvalid<T::Property>>>,
pub arguments: T::ParseArguments,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct ParseResponse<T: JmapObject> {
#[serde(rename = "accountId")]
pub account_id: Id,
#[serde(rename = "parsed")]
#[serde(skip_serializing_if = "VecMap::is_empty")]
pub parsed: VecMap<BlobId, Value<'static, T::Property, T::Element>>,
#[serde(rename = "notParsable")]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub not_parsable: Vec<BlobId>,
#[serde(rename = "notFound")]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub not_found: Vec<MaybeInvalid<BlobId>>,
}
impl<'de, T: JmapObject> DeserializeArguments<'de> for ParseRequest<T> {
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"accountId" => {
self.account_id = crate::request::deserialize_account_id(map)?;
},
b"blobIds" => {
self.blob_ids = map.next_value()?;
},
b"properties" => {
self.properties = map.next_value()?;
},
_ => {
self.arguments.deserialize_argument(key, map)?;
}
);
Ok(())
}
}
impl<'de, T: JmapObject> Deserialize<'de> for ParseRequest<T> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserialize_request(deserializer)
}
}
impl<T: JmapObject> Default for ParseRequest<T> {
fn default() -> Self {
Self {
account_id: Id::default(),
blob_ids: Vec::default(),
properties: None,
arguments: T::ParseArguments::default(),
}
}
}
+389
View File
@@ -0,0 +1,389 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
object::JmapObject,
request::deserialize::{DeserializeArguments, deserialize_request},
types::state::State,
};
use serde::{
Deserialize, Deserializer,
de::{self, DeserializeSeed, MapAccess, SeqAccess, Visitor},
};
use std::{
borrow::Cow,
fmt::{self},
};
use types::id::Id;
#[derive(Debug, Clone)]
pub struct QueryRequest<T: JmapObject> {
pub account_id: Id,
pub filter: Vec<Filter<T::Filter>>,
pub sort: Option<Vec<Comparator<T::Comparator>>>,
pub position: Option<i32>,
pub anchor: Option<Id>,
pub anchor_offset: Option<i32>,
pub limit: Option<usize>,
pub calculate_total: Option<bool>,
pub arguments: T::QueryArguments,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct QueryResponse {
#[serde(rename = "accountId")]
pub account_id: Id,
#[serde(rename = "queryState")]
pub query_state: State,
#[serde(rename = "canCalculateChanges")]
pub can_calculate_changes: bool,
#[serde(rename = "position")]
pub position: i32,
#[serde(rename = "ids")]
pub ids: Vec<Id>,
#[serde(rename = "total")]
#[serde(skip_serializing_if = "Option::is_none")]
pub total: Option<usize>,
#[serde(rename = "limit")]
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<usize>,
}
#[derive(Clone, Debug, PartialEq, Eq, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
pub enum Filter<T>
where
T: for<'de> DeserializeArguments<'de> + Default,
{
Property(T),
And,
Or,
Not,
Close,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Comparator<T>
where
T: for<'de> DeserializeArguments<'de> + Default,
{
pub is_ascending: bool,
pub collation: Option<String>,
pub property: T,
}
impl<'de, T: JmapObject> DeserializeArguments<'de> for QueryRequest<T> {
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"accountId" => {
self.account_id = crate::request::deserialize_account_id(map)?;
},
b"filter" => {
self.filter = map.next_value::<FilterWrapper<T::Filter>>()?.0;
},
b"sort" => {
self.sort = map.next_value()?;
},
b"calculateTotal" => {
self.calculate_total = map.next_value()?;
},
b"position" => {
self.position = map.next_value()?;
},
b"anchor" => {
self.anchor = map
.next_value::<Option<crate::request::MaybeInvalid<Id>>>()?
.map(|anchor| anchor.try_unwrap().unwrap_or(Id::from(u64::MAX)));
},
b"anchorOffset" => {
self.anchor_offset = map.next_value()?;
},
b"limit" => {
self.limit = map.next_value()?;
},
_ => {
self.arguments.deserialize_argument(key, map)?;
}
);
Ok(())
}
}
impl<'de, T: JmapObject> Deserialize<'de> for QueryRequest<T> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserialize_request(deserializer)
}
}
impl<T: JmapObject> Default for QueryRequest<T> {
fn default() -> Self {
Self {
account_id: Id::default(),
filter: vec![],
sort: None,
position: None,
anchor: None,
anchor_offset: None,
limit: None,
calculate_total: None,
arguments: T::QueryArguments::default(),
}
}
}
struct FilterMapCollector<'x, T: 'x>(&'x mut Vec<Filter<T>>)
where
T: for<'de> DeserializeArguments<'de> + Default;
struct FilterListCollector<'x, T: 'x>(&'x mut Vec<Filter<T>>)
where
T: for<'de> DeserializeArguments<'de> + Default;
pub struct FilterWrapper<T>(pub Vec<Filter<T>>)
where
T: for<'de> DeserializeArguments<'de> + Default;
impl<'de, T> Deserialize<'de> for FilterWrapper<T>
where
T: for<'de2> DeserializeArguments<'de2> + Default,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let mut items = Vec::new();
FilterMapCollector(&mut items)
.deserialize(deserializer)
.map(|_| FilterWrapper(items))
}
}
impl<'de, 'x, T> DeserializeSeed<'de> for FilterMapCollector<'x, T>
where
T: for<'de2> DeserializeArguments<'de2> + Default,
{
type Value = ();
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
struct FilterVisitor<'x, T: 'x>(&'x mut Vec<Filter<T>>)
where
T: for<'de2> DeserializeArguments<'de2> + Default;
impl<'de, 'x, T> Visitor<'de> for FilterVisitor<'x, T>
where
T: for<'de2> DeserializeArguments<'de2> + Default,
{
type Value = ();
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "a filter object")
}
fn visit_unit<E>(self) -> Result<(), E>
where
E: de::Error,
{
Ok(())
}
fn visit_none<E>(self) -> Result<(), E>
where
E: de::Error,
{
Ok(())
}
fn visit_map<V>(self, mut map: V) -> Result<(), V::Error>
where
V: MapAccess<'de>,
{
let mut filter = None;
let mut has_multiple_filters = false;
let mut has_conditions = None;
let mut op = None;
while let Some(key) = map.next_key::<Cow<str>>()? {
match key.len() {
8 if key == "operator" => {
let op_ = hashify::tiny_map!(
map.next_value::<&str>()?.as_bytes(),
"AND" => Filter::And,
"OR" => Filter::Or,
"NOT" => Filter::Not,
)
.ok_or_else(|| {
de::Error::custom(format!("Unknown filter operator: {}", key))
})?;
if let Some(pos) = has_conditions {
self.0[pos] = op_;
} else {
op = Some(op_);
}
}
10 if key == "conditions" => {
has_conditions = Some(self.0.len());
self.0.push(op.take().unwrap_or(Filter::And));
map.next_value_seed(FilterListCollector(self.0))?;
self.0.push(Filter::Close);
}
_ => {
if let Some(filter) = filter {
if !has_multiple_filters {
self.0.push(Filter::And);
has_multiple_filters = true;
}
self.0.push(Filter::Property(filter));
}
let mut new_filter = T::default();
new_filter.deserialize_argument(&key, &mut map)?;
filter = Some(new_filter);
}
}
}
if let Some(filter) = filter {
if has_conditions.is_some() {
return Err(de::Error::custom(
"Cannot mix conditions with property filters",
));
}
self.0.push(Filter::Property(filter));
if has_multiple_filters {
self.0.push(Filter::Close);
}
}
Ok(())
}
}
deserializer.deserialize_any(FilterVisitor(self.0))
}
}
impl<'de, 'x, T> DeserializeSeed<'de> for FilterListCollector<'x, T>
where
T: for<'de2> DeserializeArguments<'de2> + Default,
{
type Value = ();
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
struct FilterVisitor<'x, T: 'x>(&'x mut Vec<Filter<T>>)
where
T: for<'de2> DeserializeArguments<'de2> + Default;
impl<'de, 'x, T> Visitor<'de> for FilterVisitor<'x, T>
where
T: for<'de2> DeserializeArguments<'de2> + Default,
{
type Value = ();
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "a filter list")
}
fn visit_seq<A>(self, mut seq: A) -> Result<(), A::Error>
where
A: SeqAccess<'de>,
{
while let Some(()) = seq.next_element_seed(FilterMapCollector(self.0))? {}
Ok(())
}
}
deserializer.deserialize_seq(FilterVisitor(self.0))
}
}
impl<'de, T> DeserializeArguments<'de> for Comparator<T>
where
T: for<'de2> DeserializeArguments<'de2> + Default,
{
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"isAscending" => {
self.is_ascending = map.next_value()?;
},
b"collation" => {
self.collation = map.next_value()?;
},
_ => {
self.property.deserialize_argument(key, map)?;
}
);
Ok(())
}
}
impl<'de, T> Deserialize<'de> for Comparator<T>
where
T: for<'de2> DeserializeArguments<'de2> + Default,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserialize_request(deserializer)
}
}
impl<T> Comparator<T>
where
T: for<'de> DeserializeArguments<'de> + Default,
{
pub fn descending(property: T) -> Self {
Self {
property,
is_ascending: false,
collation: None,
}
}
pub fn ascending(property: T) -> Self {
Self {
property,
is_ascending: true,
collation: None,
}
}
}
impl<T> Default for Comparator<T>
where
T: for<'de> DeserializeArguments<'de> + Default,
{
fn default() -> Self {
Self {
is_ascending: true,
collation: None,
property: T::default(),
}
}
}
@@ -0,0 +1,136 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
method::query::{Comparator, Filter, FilterWrapper, QueryRequest},
object::JmapObject,
request::deserialize::{DeserializeArguments, deserialize_request},
types::state::State,
};
use serde::{Deserialize, Deserializer};
use types::id::Id;
#[derive(Debug, Clone)]
pub struct QueryChangesRequest<T: JmapObject> {
pub account_id: Id,
pub filter: Vec<Filter<T::Filter>>,
pub sort: Option<Vec<Comparator<T::Comparator>>>,
pub since_query_state: State,
pub max_changes: Option<usize>,
pub up_to_id: Option<Id>,
pub calculate_total: Option<bool>,
pub arguments: T::QueryArguments,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct QueryChangesResponse {
#[serde(rename = "accountId")]
pub account_id: Id,
#[serde(rename = "oldQueryState")]
pub old_query_state: State,
#[serde(rename = "newQueryState")]
pub new_query_state: State,
#[serde(rename = "total")]
#[serde(skip_serializing_if = "Option::is_none")]
pub total: Option<usize>,
#[serde(rename = "removed")]
pub removed: Vec<Id>,
#[serde(rename = "added")]
pub added: Vec<AddedItem>,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct AddedItem {
pub id: Id,
pub index: usize,
}
impl AddedItem {
pub fn new(id: Id, index: usize) -> Self {
Self { id, index }
}
}
impl<'de, T: JmapObject> DeserializeArguments<'de> for QueryChangesRequest<T> {
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"accountId" => {
self.account_id = crate::request::deserialize_account_id(map)?;
},
b"filter" => {
self.filter = map.next_value::<FilterWrapper<T::Filter>>()?.0;
},
b"sort" => {
self.sort = map.next_value()?;
},
b"sinceQueryState" => {
self.since_query_state = map.next_value()?;
},
b"maxChanges" => {
self.max_changes = map.next_value()?;
},
b"upToId" => {
self.up_to_id = map.next_value()?;
},
b"calculateTotal" => {
self.calculate_total = map.next_value()?;
},
_ => {
self.arguments.deserialize_argument(key, map)?;
}
);
Ok(())
}
}
impl<'de, T: JmapObject> Deserialize<'de> for QueryChangesRequest<T> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserialize_request(deserializer)
}
}
impl<T: JmapObject> Default for QueryChangesRequest<T> {
fn default() -> Self {
Self {
account_id: Id::default(),
filter: Vec::new(),
sort: None,
since_query_state: State::default(),
max_changes: None,
up_to_id: None,
calculate_total: None,
arguments: T::QueryArguments::default(),
}
}
}
impl<T: JmapObject> From<QueryChangesRequest<T>> for QueryRequest<T> {
fn from(request: QueryChangesRequest<T>) -> Self {
QueryRequest {
account_id: request.account_id,
filter: request.filter,
sort: request.sort,
position: None,
anchor: None,
anchor_offset: None,
limit: None,
calculate_total: request.calculate_total,
arguments: request.arguments,
}
}
}
@@ -0,0 +1,93 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::query::Filter;
use crate::{
method::query::FilterWrapper,
object::email::EmailFilter,
request::{
MaybeInvalid,
deserialize::{DeserializeArguments, deserialize_request},
reference::{MaybeResultReference, ResultReference},
},
};
use serde::{Deserialize, Deserializer};
use types::id::Id;
#[derive(Debug, Clone)]
pub struct GetSearchSnippetRequest {
pub account_id: Id,
pub filter: Vec<Filter<EmailFilter>>,
pub email_ids: MaybeResultReference<Vec<MaybeInvalid<Id>>>,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct GetSearchSnippetResponse {
#[serde(rename = "accountId")]
pub account_id: Id,
#[serde(rename = "list")]
pub list: Vec<SearchSnippet>,
#[serde(rename = "notFound")]
pub not_found: Option<Vec<MaybeInvalid<Id>>>,
}
#[derive(serde::Serialize, Clone, Debug)]
pub struct SearchSnippet {
#[serde(rename = "emailId")]
pub email_id: Id,
pub subject: Option<String>,
pub preview: Option<String>,
}
impl<'de> DeserializeArguments<'de> for GetSearchSnippetRequest {
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"accountId" => {
self.account_id = crate::request::deserialize_account_id(map)?;
},
b"filter" => {
self.filter = map.next_value::<FilterWrapper<EmailFilter>>()?.0;
},
b"emailIds" => {
self.email_ids = MaybeResultReference::Value(map.next_value::<Vec<MaybeInvalid<Id>>>()?);
},
b"#emailIds" => {
self.email_ids = MaybeResultReference::Reference(map.next_value::<ResultReference>()?);
},
_ => {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
);
Ok(())
}
}
impl<'de> Deserialize<'de> for GetSearchSnippetRequest {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserialize_request(deserializer)
}
}
impl Default for GetSearchSnippetRequest {
fn default() -> Self {
Self {
account_id: Id::default(),
filter: Vec::new(),
email_ids: MaybeResultReference::Value(Vec::new()),
}
}
}
+299
View File
@@ -0,0 +1,299 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::ahash_is_empty;
use crate::{
error::set::{InvalidProperty, SetError},
object::{JmapObject, JmapObjectId},
request::{
MaybeInvalid,
deserialize::{DeserializeArguments, deserialize_request},
reference::{MaybeResultReference, ResultReference},
},
response::Response,
types::state::State,
};
use ahash::AHashMap;
use jmap_tools::{Key, Map, Value};
use serde::{Deserialize, Deserializer};
use types::id::Id;
use utils::map::vec_map::VecMap;
#[derive(Debug, Clone)]
#[allow(clippy::type_complexity)]
pub struct SetRequest<'x, T: JmapObject> {
pub account_id: Id,
pub if_in_state: Option<State>,
pub create: Option<VecMap<String, Value<'x, T::Property, T::Element>>>,
pub update: Option<VecMap<MaybeInvalid<Id>, Value<'x, T::Property, T::Element>>>,
pub destroy: Option<MaybeResultReference<Vec<MaybeInvalid<Id>>>>,
pub arguments: T::SetArguments<'x>,
}
#[derive(Debug, Clone, Default, serde::Serialize)]
#[allow(clippy::type_complexity)]
pub struct SetResponse<T: JmapObject> {
#[serde(rename = "accountId")]
#[serde(skip_serializing_if = "Option::is_none")]
pub account_id: Option<Id>,
#[serde(rename = "oldState")]
#[serde(skip_serializing_if = "Option::is_none")]
pub old_state: Option<State>,
#[serde(rename = "newState")]
#[serde(skip_serializing_if = "Option::is_none")]
pub new_state: Option<State>,
#[serde(rename = "created")]
#[serde(skip_serializing_if = "ahash_is_empty")]
pub created: AHashMap<String, Value<'static, T::Property, T::Element>>,
#[serde(rename = "updated")]
#[serde(skip_serializing_if = "VecMap::is_empty")]
pub updated: VecMap<Id, Option<Value<'static, T::Property, T::Element>>>,
#[serde(rename = "destroyed")]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub destroyed: Vec<Id>,
#[serde(rename = "notCreated")]
#[serde(skip_serializing_if = "VecMap::is_empty")]
pub not_created: VecMap<String, SetError<T::Property>>,
#[serde(rename = "notUpdated")]
#[serde(skip_serializing_if = "VecMap::is_empty")]
pub not_updated: VecMap<MaybeInvalid<Id>, SetError<T::Property>>,
#[serde(rename = "notDestroyed")]
#[serde(skip_serializing_if = "VecMap::is_empty")]
pub not_destroyed: VecMap<MaybeInvalid<Id>, SetError<T::Property>>,
}
impl<'de, T: JmapObject> DeserializeArguments<'de> for SetRequest<'de, T> {
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"accountId" => {
self.account_id = crate::request::deserialize_account_id(map)?;
},
b"ifInState" => {
self.if_in_state = map.next_value()?;
},
b"create" => {
self.create = map.next_value()?;
},
b"update" => {
self.update = map.next_value()?;
},
b"destroy" => {
self.destroy = map.next_value::<Option<Vec<MaybeInvalid<Id>>>>()?.map(MaybeResultReference::Value);
},
b"#destroy" => {
self.destroy = Some(MaybeResultReference::Reference(map.next_value::<ResultReference>()?));
}
_ => {
self.arguments.deserialize_argument(key, map)?;
}
);
Ok(())
}
}
impl<'de, T: JmapObject> Deserialize<'de> for SetRequest<'de, T> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserialize_request(deserializer)
}
}
impl<'x, T: JmapObject> Default for SetRequest<'x, T> {
fn default() -> Self {
Self {
account_id: Id::default(),
if_in_state: None,
create: None,
update: None,
destroy: None,
arguments: T::SetArguments::default(),
}
}
}
impl<'x, T: JmapObject> SetRequest<'x, T> {
pub fn validate(&self, max_objects_in_set: usize) -> trc::Result<()> {
if self.create.as_ref().map_or(0, |objs| objs.len())
+ self.update.as_ref().map_or(0, |objs| objs.len())
+ self.destroy.as_ref().map_or(0, |objs| {
if let MaybeResultReference::Value(ids) = objs {
ids.len()
} else {
0
}
})
> max_objects_in_set
{
Err(trc::JmapEvent::RequestTooLarge.into_err())
} else {
Ok(())
}
}
pub fn has_updates(&self) -> bool {
self.update.as_ref().is_some_and(|objs| !objs.is_empty())
}
pub fn has_creates(&self) -> bool {
self.create.as_ref().is_some_and(|objs| !objs.is_empty())
}
pub fn unwrap_create(&mut self) -> VecMap<String, Value<'x, T::Property, T::Element>> {
self.create.take().unwrap_or_default()
}
pub fn unwrap_update(
&mut self,
) -> VecMap<MaybeInvalid<Id>, Value<'x, T::Property, T::Element>> {
self.update.take().unwrap_or_default()
}
pub fn unwrap_destroy(&mut self) -> Vec<MaybeInvalid<Id>> {
self.destroy
.take()
.map(|ids| ids.unwrap())
.unwrap_or_default()
}
}
impl<T: JmapObject> SetResponse<T> {
pub fn from_request(request: &SetRequest<T>, max_objects: usize) -> trc::Result<Self> {
let n_create = request.create.as_ref().map_or(0, |objs| objs.len());
let n_update = request.update.as_ref().map_or(0, |objs| objs.len());
let n_destroy = request.destroy.as_ref().map_or(0, |objs| {
if let MaybeResultReference::Value(ids) = objs {
ids.len()
} else {
0
}
});
if n_create + n_update + n_destroy <= max_objects {
Ok(SetResponse {
account_id: if request.account_id.is_valid() {
request.account_id.into()
} else {
None
},
new_state: None,
old_state: None,
created: AHashMap::with_capacity(n_create),
updated: VecMap::with_capacity(n_update),
destroyed: Vec::with_capacity(n_destroy),
not_created: VecMap::new(),
not_updated: VecMap::new(),
not_destroyed: VecMap::new(),
})
} else {
Err(trc::JmapEvent::RequestTooLarge.into_err())
}
}
pub fn with_state(mut self, state: State) -> Self {
self.old_state = Some(state.clone());
self.new_state = Some(state);
self
}
pub fn collect_will_destroy(&mut self, ids: Vec<MaybeInvalid<Id>>) -> Vec<Id> {
let mut will_destroy = Vec::with_capacity(ids.len());
for id in ids {
match id {
MaybeInvalid::Value(id) => will_destroy.push(id),
invalid => self.not_destroyed.append(invalid, SetError::not_found()),
}
}
will_destroy
}
pub fn created(&mut self, id: String, document_id: impl Into<T::Id>) {
self.created.insert(
id,
Value::Object(Map::from(vec![(
Key::Property(T::ID_PROPERTY),
Value::Element(document_id.into().into()),
)])),
);
}
pub fn invalid_property_create(
&mut self,
id: String,
property: impl Into<InvalidProperty<T::Property>>,
) {
self.not_created.append(
id,
SetError::invalid_properties()
.with_property(property)
.with_description("Invalid property or value.".to_string()),
);
}
pub fn invalid_property_update(
&mut self,
id: Id,
property: impl Into<InvalidProperty<T::Property>>,
) {
self.not_updated.append(
id,
SetError::invalid_properties()
.with_property(property)
.with_description("Invalid property or value.".to_string()),
);
}
pub fn update_created_ids(&self, response: &mut Response) {
for (user_id, obj) in &self.created {
if let Value::Object(obj) = obj
&& let Some(Value::Element(id)) = obj.get(&Key::Property(T::ID_PROPERTY))
&& let Some(id) = id.as_any_id()
{
response.created_ids.insert(user_id.clone(), id);
}
}
}
pub fn get_object_by_id(
&mut self,
id: Id,
) -> Option<&mut Value<'static, T::Property, T::Element>> {
if let Some(obj) = self.updated.get_mut(&id) {
if let Some(obj) = obj {
return Some(obj);
} else {
*obj = Some(Value::Object(Map::with_capacity(1)));
return obj.as_mut().unwrap().into();
}
}
(&mut self.created)
.into_iter()
.map(|(_, obj)| obj)
.find(|obj| {
obj.as_object_and_get(&Key::Property(T::ID_PROPERTY))
.and_then(|v| v.as_element())
.and_then(|v| v.as_id())
.is_some_and(|oid| oid == id)
})
}
pub fn has_changes(&self) -> bool {
!self.created.is_empty() || !self.updated.is_empty() || !self.destroyed.is_empty()
}
}
+212
View File
@@ -0,0 +1,212 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::borrow::Cow;
use super::ahash_is_empty;
use crate::{
error::set::SetError,
object::{AnyId, blob::BlobProperty},
request::{
deserialize::{DeserializeArguments, deserialize_request},
reference::MaybeIdReference,
},
response::Response,
};
use ahash::AHashMap;
use mail_parser::decoders::base64::base64_decode;
use serde::{Deserialize, Deserializer};
use types::{blob::BlobId, id::Id};
use utils::map::vec_map::VecMap;
#[derive(Debug, Clone, Default)]
pub struct BlobUploadRequest {
pub account_id: Id,
pub create: VecMap<String, UploadObject>,
}
#[derive(Debug, Clone, Default)]
pub struct UploadObject {
pub type_: Option<String>,
pub data: Vec<DataSourceObject>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum DataSourceObject {
Id {
id: MaybeIdReference<BlobId>,
length: Option<usize>,
offset: Option<usize>,
},
Value(Vec<u8>),
#[default]
Null,
}
#[derive(Debug, Clone, Default, serde::Serialize)]
pub struct BlobUploadResponse {
#[serde(rename = "accountId")]
pub account_id: Id,
#[serde(rename = "created")]
#[serde(skip_serializing_if = "ahash_is_empty")]
pub created: AHashMap<String, BlobUploadResponseObject>,
#[serde(rename = "notCreated")]
#[serde(skip_serializing_if = "VecMap::is_empty")]
pub not_created: VecMap<String, SetError<BlobProperty>>,
}
#[derive(Debug, Clone, Default, serde::Serialize)]
pub struct BlobUploadResponseObject {
pub id: BlobId,
#[serde(rename = "type")]
#[serde(skip_serializing_if = "Option::is_none")]
pub type_: Option<String>,
pub size: usize,
}
impl<'de> DeserializeArguments<'de> for BlobUploadRequest {
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"accountId" => {
self.account_id = crate::request::deserialize_account_id(map)?;
},
b"create" => {
self.create = map.next_value()?;
}
_ => {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
);
Ok(())
}
}
impl<'de> DeserializeArguments<'de> for UploadObject {
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"type" => {
self.type_ = map.next_value()?;
},
b"data" => {
self.data = map.next_value()?;
},
_ => {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
);
Ok(())
}
}
impl<'de> DeserializeArguments<'de> for DataSourceObject {
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"data:asText" => {
*self = DataSourceObject::Value(map.next_value::<String>().map(|v| v.into_bytes())?);
},
b"data:asBase64" => {
*self = DataSourceObject::Value(base64_decode(map.next_value::<Cow<'_, str>>()?.as_bytes()).ok_or_else(|| serde::de::Error::custom("Failed to decode base64 data"))?);
},
b"blobId" => {
match self {
DataSourceObject::Id { id, .. } => {
*id = map.next_value()?;
},
_ => {
*self = DataSourceObject::Id {
id: map.next_value()?,
length: None,
offset: None,
};
}
}
},
b"offset" => {
match self {
DataSourceObject::Id { offset, .. } => {
*offset = map.next_value()?;
},
_ => {
*self = DataSourceObject::Id {
id: MaybeIdReference::Invalid("".into()),
length: None,
offset: map.next_value()?,
};
}
}
},
b"length" => {
match self {
DataSourceObject::Id { length, .. } => {
*length = map.next_value()?;
},
_ => {
*self = DataSourceObject::Id {
id: MaybeIdReference::Invalid("".into()),
length: map.next_value()?,
offset: None,
};
}
}
},
_ => {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
);
Ok(())
}
}
impl BlobUploadResponse {
pub fn update_created_ids(&self, response: &mut Response) {
for (user_id, obj) in &self.created {
response
.created_ids
.insert(user_id.clone(), AnyId::BlobId(obj.id.clone()));
}
}
}
impl<'de> Deserialize<'de> for DataSourceObject {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserialize_request(deserializer)
}
}
impl<'de> Deserialize<'de> for UploadObject {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserialize_request(deserializer)
}
}
impl<'de> Deserialize<'de> for BlobUploadRequest {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserialize_request(deserializer)
}
}
+59
View File
@@ -0,0 +1,59 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
error::set::SetError,
object::sieve::SieveProperty,
request::{
MaybeInvalid,
deserialize::{DeserializeArguments, deserialize_request},
},
};
use serde::{Deserialize, Deserializer, Serialize};
use types::{blob::BlobId, id::Id};
#[derive(Debug, Clone, Default)]
pub struct ValidateSieveScriptRequest {
pub account_id: Id,
pub blob_id: MaybeInvalid<BlobId>,
}
#[derive(Debug, Serialize)]
pub struct ValidateSieveScriptResponse {
#[serde(rename = "accountId")]
pub account_id: Id,
pub error: Option<SetError<SieveProperty>>,
}
impl<'de> DeserializeArguments<'de> for ValidateSieveScriptRequest {
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"accountId" => {
self.account_id = crate::request::deserialize_account_id(map)?;
},
b"blobId" => {
self.blob_id = map.next_value()?;
},
_ => {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
);
Ok(())
}
}
impl<'de> Deserialize<'de> for ValidateSieveScriptRequest {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserialize_request(deserializer)
}
}
+354
View File
@@ -0,0 +1,354 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
object::{
AnyId, JmapObject, JmapObjectId, JmapRight, JmapSharedObject, MaybeReference, parse_ref,
},
request::{deserialize::DeserializeArguments, reference::MaybeIdReference},
};
use jmap_tools::{Element, JsonPointer, JsonPointerItem, Key, Property};
use std::{borrow::Cow, str::FromStr};
use types::{acl::Acl, id::Id, special_use::SpecialUse};
#[derive(Debug, Clone, Default)]
pub struct AddressBook;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum AddressBookProperty {
Id,
Name,
Description,
SortOrder,
IsDefault,
IsSubscribed,
ShareWith,
MyRights,
// Other
IdValue(Id),
Rights(AddressBookRight),
Pointer(JsonPointer<AddressBookProperty>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum AddressBookRight {
MayRead,
MayWrite,
MayShare,
MayDelete,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum AddressBookValue {
Id(Id),
IdReference(String),
Role(SpecialUse),
}
impl Property for AddressBookProperty {
fn try_parse(key: Option<&Key<'_, Self>>, value: &str) -> Option<Self> {
let allow_patch = key.is_none();
if let Some(Key::Property(key)) = key {
match key.patch_or_prop() {
AddressBookProperty::ShareWith => {
Id::from_str(value).ok().map(AddressBookProperty::IdValue)
}
_ => AddressBookProperty::parse(value, allow_patch),
}
} else {
AddressBookProperty::parse(value, allow_patch)
}
}
fn to_cow(&self) -> Cow<'static, str> {
match self {
AddressBookProperty::Id => "id",
AddressBookProperty::Name => "name",
AddressBookProperty::Description => "description",
AddressBookProperty::SortOrder => "sortOrder",
AddressBookProperty::IsDefault => "isDefault",
AddressBookProperty::IsSubscribed => "isSubscribed",
AddressBookProperty::ShareWith => "shareWith",
AddressBookProperty::MyRights => "myRights",
AddressBookProperty::Rights(addressbook_right) => addressbook_right.as_str(),
AddressBookProperty::Pointer(json_pointer) => return json_pointer.to_string().into(),
AddressBookProperty::IdValue(id) => return id.to_string().into(),
}
.into()
}
}
impl AddressBookRight {
pub fn as_str(&self) -> &'static str {
match self {
AddressBookRight::MayRead => "mayRead",
AddressBookRight::MayWrite => "mayWrite",
AddressBookRight::MayShare => "mayShare",
AddressBookRight::MayDelete => "mayDelete",
}
}
}
impl Element for AddressBookValue {
type Property = AddressBookProperty;
fn try_parse<P>(key: &Key<'_, Self::Property>, value: &str) -> Option<Self> {
if let Key::Property(prop) = key {
match prop.patch_or_prop() {
AddressBookProperty::Id => match parse_ref(value) {
MaybeReference::Value(v) => Some(AddressBookValue::Id(v)),
MaybeReference::Reference(v) => Some(AddressBookValue::IdReference(v)),
MaybeReference::ParseError => None,
},
_ => None,
}
} else {
None
}
}
fn to_cow(&self) -> Cow<'static, str> {
match self {
AddressBookValue::Id(id) => id.to_string().into(),
AddressBookValue::IdReference(r) => format!("#{r}").into(),
AddressBookValue::Role(special_use) => special_use.as_str().unwrap_or_default().into(),
}
}
}
impl AddressBookProperty {
fn parse(value: &str, allow_patch: bool) -> Option<Self> {
hashify::tiny_map!(value.as_bytes(),
b"id" => AddressBookProperty::Id,
b"name" => AddressBookProperty::Name,
b"description" => AddressBookProperty::Description,
b"sortOrder" => AddressBookProperty::SortOrder,
b"isDefault" => AddressBookProperty::IsDefault,
b"isSubscribed" => AddressBookProperty::IsSubscribed,
b"shareWith" => AddressBookProperty::ShareWith,
b"myRights" => AddressBookProperty::MyRights,
b"mayRead" => AddressBookProperty::Rights(AddressBookRight::MayRead),
b"mayWrite" => AddressBookProperty::Rights(AddressBookRight::MayWrite),
b"mayShare" => AddressBookProperty::Rights(AddressBookRight::MayShare),
b"mayDelete" => AddressBookProperty::Rights(AddressBookRight::MayDelete)
)
.or_else(|| {
if allow_patch && value.contains('/') {
AddressBookProperty::Pointer(JsonPointer::parse(value)).into()
} else {
None
}
})
}
fn patch_or_prop(&self) -> &AddressBookProperty {
if let AddressBookProperty::Pointer(ptr) = self
&& let Some(JsonPointerItem::Key(Key::Property(prop))) = ptr.last()
{
prop
} else {
self
}
}
}
#[derive(Debug, Clone, Default)]
pub struct AddressBookSetArguments {
pub on_destroy_remove_contents: Option<bool>,
pub on_success_set_is_default: Option<MaybeIdReference<Id>>,
}
impl<'de> DeserializeArguments<'de> for AddressBookSetArguments {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'de>,
{
hashify::fnc_map!(key.as_bytes(),
b"onDestroyRemoveContents" => {
self.on_destroy_remove_contents = map.next_value()?;
},
b"onSuccessSetIsDefault" => {
self.on_success_set_is_default = map.next_value()?;
},
_ => {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
);
Ok(())
}
}
impl FromStr for AddressBookProperty {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
AddressBookProperty::parse(s, false).ok_or(())
}
}
impl JmapObject for AddressBook {
type Property = AddressBookProperty;
type Element = AddressBookValue;
type Id = Id;
type Filter = ();
type Comparator = ();
type GetArguments = ();
type SetArguments<'de> = AddressBookSetArguments;
type QueryArguments = ();
type CopyArguments = ();
type ParseArguments = ();
const ID_PROPERTY: Self::Property = AddressBookProperty::Id;
}
impl JmapSharedObject for AddressBook {
type Right = AddressBookRight;
const SHARE_WITH_PROPERTY: Self::Property = AddressBookProperty::ShareWith;
}
impl From<Id> for AddressBookProperty {
fn from(id: Id) -> Self {
AddressBookProperty::IdValue(id)
}
}
impl TryFrom<AddressBookProperty> for Id {
type Error = ();
fn try_from(value: AddressBookProperty) -> Result<Self, Self::Error> {
if let AddressBookProperty::IdValue(id) = value {
Ok(id)
} else {
Err(())
}
}
}
impl TryFrom<AddressBookProperty> for AddressBookRight {
type Error = ();
fn try_from(value: AddressBookProperty) -> Result<Self, Self::Error> {
if let AddressBookProperty::Rights(right) = value {
Ok(right)
} else {
Err(())
}
}
}
impl From<Id> for AddressBookValue {
fn from(id: Id) -> Self {
AddressBookValue::Id(id)
}
}
impl JmapObjectId for AddressBookValue {
fn as_id(&self) -> Option<Id> {
if let AddressBookValue::Id(id) = self {
Some(*id)
} else {
None
}
}
fn as_any_id(&self) -> Option<AnyId> {
if let AddressBookValue::Id(id) = self {
Some(AnyId::Id(*id))
} else {
None
}
}
fn as_id_ref(&self) -> Option<&str> {
if let AddressBookValue::IdReference(r) = self {
Some(r)
} else {
None
}
}
fn try_set_id(&mut self, new_id: AnyId) -> bool {
if let AnyId::Id(new_id) = new_id {
*self = AddressBookValue::Id(new_id);
return true;
}
false
}
}
impl JmapRight for AddressBookRight {
fn to_acl(&self) -> &'static [Acl] {
match self {
AddressBookRight::MayDelete => &[Acl::Delete, Acl::RemoveItems],
AddressBookRight::MayShare => &[Acl::Share],
AddressBookRight::MayRead => &[Acl::Read, Acl::ReadItems],
AddressBookRight::MayWrite => &[Acl::Modify, Acl::AddItems, Acl::ModifyItems],
}
}
fn all_rights() -> &'static [Self] {
&[
AddressBookRight::MayRead,
AddressBookRight::MayWrite,
AddressBookRight::MayDelete,
AddressBookRight::MayShare,
]
}
}
impl From<AddressBookRight> for AddressBookProperty {
fn from(right: AddressBookRight) -> Self {
AddressBookProperty::Rights(right)
}
}
impl JmapObjectId for AddressBookProperty {
fn as_id(&self) -> Option<Id> {
if let AddressBookProperty::IdValue(id) = self {
Some(*id)
} else {
None
}
}
fn as_any_id(&self) -> Option<AnyId> {
if let AddressBookProperty::IdValue(id) = self {
Some(AnyId::Id(*id))
} else {
None
}
}
fn as_id_ref(&self) -> Option<&str> {
None
}
fn try_set_id(&mut self, new_id: AnyId) -> bool {
if let AnyId::Id(new_id) = new_id {
*self = AddressBookProperty::IdValue(new_id);
return true;
}
false
}
}
impl std::fmt::Display for AddressBookProperty {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_cow())
}
}
+233
View File
@@ -0,0 +1,233 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
object::{AnyId, JmapObject, JmapObjectId, MaybeReference, parse_ref},
request::deserialize::DeserializeArguments,
};
use jmap_tools::{Element, Key, Property};
use std::{borrow::Cow, str::FromStr};
use types::{blob::BlobId, id::Id};
#[derive(Debug, Clone, Default)]
pub struct Blob;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum BlobProperty {
Id,
BlobId,
Type,
Size,
Digest(DigestProperty),
Data(DataProperty),
IsEncodingProblem,
IsTruncated,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum DigestProperty {
Sha,
Sha256,
Sha512,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum DataProperty {
AsText,
AsBase64,
Default,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum BlobValue {
BlobId(BlobId),
IdReference(String),
}
impl Property for BlobProperty {
fn try_parse(_: Option<&Key<'_, Self>>, value: &str) -> Option<Self> {
BlobProperty::parse(value)
}
fn to_cow(&self) -> Cow<'static, str> {
match self {
BlobProperty::BlobId => "blobId",
BlobProperty::Id => "id",
BlobProperty::Size => "size",
BlobProperty::Type => "type",
BlobProperty::IsEncodingProblem => "isEncodingProblem",
BlobProperty::IsTruncated => "isTruncated",
BlobProperty::Data(data) => match data {
DataProperty::AsText => "data:asText",
DataProperty::AsBase64 => "data:asBase64",
DataProperty::Default => "data",
},
BlobProperty::Digest(digest) => match digest {
DigestProperty::Sha => "digest:sha",
DigestProperty::Sha256 => "digest:sha-256",
DigestProperty::Sha512 => "digest:sha-512",
},
}
.into()
}
}
impl Element for BlobValue {
type Property = BlobProperty;
fn try_parse<P>(key: &Key<'_, Self::Property>, value: &str) -> Option<Self> {
if let Key::Property(prop) = key {
match prop {
BlobProperty::BlobId => match parse_ref(value) {
MaybeReference::Value(v) => Some(BlobValue::BlobId(v)),
MaybeReference::Reference(v) => Some(BlobValue::IdReference(v)),
MaybeReference::ParseError => None,
},
_ => None,
}
} else {
None
}
}
fn to_cow(&self) -> Cow<'static, str> {
match self {
BlobValue::BlobId(blob_id) => blob_id.to_string().into(),
BlobValue::IdReference(r) => format!("#{r}").into(),
}
}
}
impl BlobProperty {
fn parse(value: &str) -> Option<Self> {
hashify::tiny_map!(value.as_bytes(),
b"blobId" => BlobProperty::BlobId,
b"id" => BlobProperty::Id,
b"size" => BlobProperty::Size,
b"type" => BlobProperty::Type,
b"isEncodingProblem" => BlobProperty::IsEncodingProblem,
b"isTruncated" => BlobProperty::IsTruncated,
b"data:asText" => BlobProperty::Data(DataProperty::AsText),
b"data:asBase64" => BlobProperty::Data(DataProperty::AsBase64),
b"data" => BlobProperty::Data(DataProperty::Default),
b"digest:sha" => BlobProperty::Digest(DigestProperty::Sha),
b"digest:sha-256" => BlobProperty::Digest(DigestProperty::Sha256),
b"digest:sha-512" => BlobProperty::Digest(DigestProperty::Sha512),
)
}
}
impl FromStr for BlobProperty {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
BlobProperty::parse(s).ok_or(())
}
}
#[derive(Debug, Clone, Default)]
pub struct BlobGetArguments {
pub offset: Option<usize>,
pub length: Option<usize>,
}
impl<'de> DeserializeArguments<'de> for BlobGetArguments {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'de>,
{
hashify::fnc_map!(key.as_bytes(),
b"offset" => {
self.offset = map.next_value()?;
},
b"length" => {
self.length = map.next_value()?;
},
_ => {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
);
Ok(())
}
}
impl JmapObject for Blob {
type Property = BlobProperty;
type Element = BlobValue;
type Id = BlobId;
type Filter = ();
type Comparator = ();
type GetArguments = BlobGetArguments;
type SetArguments<'de> = ();
type QueryArguments = ();
type CopyArguments = ();
type ParseArguments = ();
const ID_PROPERTY: Self::Property = BlobProperty::Id;
}
impl From<BlobId> for BlobValue {
fn from(id: BlobId) -> Self {
BlobValue::BlobId(id)
}
}
impl JmapObjectId for BlobValue {
fn as_id(&self) -> Option<Id> {
None
}
fn as_any_id(&self) -> Option<AnyId> {
match self {
BlobValue::BlobId(id) => Some(AnyId::BlobId(id.clone())),
_ => None,
}
}
fn as_id_ref(&self) -> Option<&str> {
if let BlobValue::IdReference(r) = self {
Some(r)
} else {
None
}
}
fn try_set_id(&mut self, new_id: AnyId) -> bool {
if let AnyId::BlobId(id) = new_id {
*self = BlobValue::BlobId(id);
return true;
}
false
}
}
impl JmapObjectId for BlobProperty {
fn as_id(&self) -> Option<Id> {
None
}
fn as_any_id(&self) -> Option<AnyId> {
None
}
fn as_id_ref(&self) -> Option<&str> {
None
}
fn try_set_id(&mut self, _: AnyId) -> bool {
false
}
}
+475
View File
@@ -0,0 +1,475 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
object::{
AnyId, JmapObject, JmapObjectId, JmapRight, JmapSharedObject, MaybeReference, parse_ref,
},
request::{deserialize::DeserializeArguments, reference::MaybeIdReference},
types::date::UTCDate,
};
use calcard::{
common::{IanaParse, timezone::Tz},
icalendar::ICalendarDuration,
jscalendar::{JSCalendarAlertAction, JSCalendarRelativeTo, JSCalendarType},
};
use jmap_tools::{Element, JsonPointer, JsonPointerItem, Key, Property};
use std::{borrow::Cow, fmt::Display, str::FromStr};
use types::{acl::Acl, id::Id};
#[derive(Debug, Clone, Default)]
pub struct Calendar;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum CalendarProperty {
Id,
Name,
Description,
Color,
SortOrder,
IsSubscribed,
IsVisible,
IsDefault,
IncludeInAvailability,
DefaultAlertsWithTime,
DefaultAlertsWithoutTime,
TimeZone,
ShareWith,
MyRights,
// Alert object properties
When,
Trigger,
Offset,
RelativeTo,
Action,
Type,
// Other
IdValue(Id),
Rights(CalendarRight),
Pointer(JsonPointer<CalendarProperty>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum CalendarRight {
MayReadFreeBusy,
MayReadItems,
MayWriteAll,
MayWriteOwn,
MayUpdatePrivate,
MayRSVP,
MayShare,
MayDelete,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum CalendarValue {
Id(Id),
IdReference(String),
IncludeInAvailability(IncludeInAvailability),
Date(UTCDate),
Timezone(Tz),
Action(JSCalendarAlertAction),
RelativeTo(JSCalendarRelativeTo),
Type(JSCalendarType),
Duration(ICalendarDuration),
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum IncludeInAvailability {
#[default]
All,
Attending,
None,
}
impl Property for CalendarProperty {
fn try_parse(key: Option<&Key<'_, Self>>, value: &str) -> Option<Self> {
let allow_patch = key.is_none();
if let Some(Key::Property(key)) = key {
match key.patch_or_prop() {
CalendarProperty::ShareWith => {
Id::from_str(value).ok().map(CalendarProperty::IdValue)
}
_ => CalendarProperty::parse(value, allow_patch),
}
} else {
CalendarProperty::parse(value, allow_patch)
}
}
fn to_cow(&self) -> Cow<'static, str> {
match self {
CalendarProperty::Id => "id",
CalendarProperty::Name => "name",
CalendarProperty::Description => "description",
CalendarProperty::Color => "color",
CalendarProperty::SortOrder => "sortOrder",
CalendarProperty::IsSubscribed => "isSubscribed",
CalendarProperty::IsVisible => "isVisible",
CalendarProperty::IsDefault => "isDefault",
CalendarProperty::IncludeInAvailability => "includeInAvailability",
CalendarProperty::DefaultAlertsWithTime => "defaultAlertsWithTime",
CalendarProperty::DefaultAlertsWithoutTime => "defaultAlertsWithoutTime",
CalendarProperty::TimeZone => "timeZone",
CalendarProperty::ShareWith => "shareWith",
CalendarProperty::MyRights => "myRights",
CalendarProperty::When => "when",
CalendarProperty::Trigger => "trigger",
CalendarProperty::Offset => "offset",
CalendarProperty::RelativeTo => "relativeTo",
CalendarProperty::Action => "action",
CalendarProperty::Type => "@type",
CalendarProperty::Rights(calendar_right) => calendar_right.as_str(),
CalendarProperty::Pointer(json_pointer) => return json_pointer.to_string().into(),
CalendarProperty::IdValue(id) => return id.to_string().into(),
}
.into()
}
}
impl CalendarRight {
pub fn as_str(&self) -> &'static str {
match self {
CalendarRight::MayReadFreeBusy => "mayReadFreeBusy",
CalendarRight::MayReadItems => "mayReadItems",
CalendarRight::MayWriteAll => "mayWriteAll",
CalendarRight::MayWriteOwn => "mayWriteOwn",
CalendarRight::MayUpdatePrivate => "mayUpdatePrivate",
CalendarRight::MayRSVP => "mayRSVP",
CalendarRight::MayShare => "mayShare",
CalendarRight::MayDelete => "mayDelete",
}
}
}
impl IncludeInAvailability {
fn parse(value: &str) -> Option<Self> {
hashify::tiny_map!(value.as_bytes(),
b"all" => IncludeInAvailability::All,
b"attending" => IncludeInAvailability::Attending,
b"none" => IncludeInAvailability::None,
)
}
pub fn as_str(&self) -> &'static str {
match self {
IncludeInAvailability::All => "all",
IncludeInAvailability::Attending => "attending",
IncludeInAvailability::None => "none",
}
}
}
impl Element for CalendarValue {
type Property = CalendarProperty;
fn try_parse<P>(key: &Key<'_, Self::Property>, value: &str) -> Option<Self> {
if let Key::Property(prop) = key {
match prop.patch_or_prop() {
CalendarProperty::Id => match parse_ref(value) {
MaybeReference::Value(v) => Some(CalendarValue::Id(v)),
MaybeReference::Reference(v) => Some(CalendarValue::IdReference(v)),
MaybeReference::ParseError => None,
},
CalendarProperty::TimeZone => Tz::from_str(value).ok().map(CalendarValue::Timezone),
CalendarProperty::IncludeInAvailability => {
IncludeInAvailability::parse(value).map(CalendarValue::IncludeInAvailability)
}
CalendarProperty::Action => JSCalendarAlertAction::from_str(value)
.ok()
.map(CalendarValue::Action),
CalendarProperty::RelativeTo => JSCalendarRelativeTo::from_str(value)
.ok()
.map(CalendarValue::RelativeTo),
CalendarProperty::When => UTCDate::from_str(value).ok().map(CalendarValue::Date),
CalendarProperty::Offset => {
ICalendarDuration::parse(value.as_bytes()).map(CalendarValue::Duration)
}
_ => None,
}
} else {
None
}
}
fn to_cow(&self) -> Cow<'static, str> {
match self {
CalendarValue::Id(id) => id.to_string().into(),
CalendarValue::IdReference(r) => format!("#{r}").into(),
CalendarValue::IncludeInAvailability(include) => include.as_str().into(),
CalendarValue::Date(date) => date.to_string().into(),
CalendarValue::Action(action) => action.as_str().into(),
CalendarValue::RelativeTo(relative) => relative.as_str().into(),
CalendarValue::Type(typ) => typ.as_str().into(),
CalendarValue::Duration(dur) => dur.to_string().into(),
CalendarValue::Timezone(tz) => tz.name().unwrap_or_default(),
}
}
}
impl CalendarProperty {
fn parse(value: &str, allow_patch: bool) -> Option<Self> {
hashify::tiny_map!(value.as_bytes(),
b"id" => CalendarProperty::Id,
b"name" => CalendarProperty::Name,
b"description" => CalendarProperty::Description,
b"color" => CalendarProperty::Color,
b"sortOrder" => CalendarProperty::SortOrder,
b"isSubscribed" => CalendarProperty::IsSubscribed,
b"isVisible" => CalendarProperty::IsVisible,
b"isDefault" => CalendarProperty::IsDefault,
b"includeInAvailability" => CalendarProperty::IncludeInAvailability,
b"defaultAlertsWithTime" => CalendarProperty::DefaultAlertsWithTime,
b"defaultAlertsWithoutTime" => CalendarProperty::DefaultAlertsWithoutTime,
b"timeZone" => CalendarProperty::TimeZone,
b"shareWith" => CalendarProperty::ShareWith,
b"myRights" => CalendarProperty::MyRights,
b"mayReadFreeBusy" => CalendarProperty::Rights(CalendarRight::MayReadFreeBusy),
b"mayReadItems" => CalendarProperty::Rights(CalendarRight::MayReadItems),
b"mayWriteAll" => CalendarProperty::Rights(CalendarRight::MayWriteAll),
b"mayWriteOwn" => CalendarProperty::Rights(CalendarRight::MayWriteOwn),
b"mayUpdatePrivate" => CalendarProperty::Rights(CalendarRight::MayUpdatePrivate),
b"mayRSVP" => CalendarProperty::Rights(CalendarRight::MayRSVP),
b"mayShare" => CalendarProperty::Rights(CalendarRight::MayShare),
b"mayDelete" => CalendarProperty::Rights(CalendarRight::MayDelete),
b"@type" => CalendarProperty::Type,
b"when" => CalendarProperty::When,
b"trigger" => CalendarProperty::Trigger,
b"offset" => CalendarProperty::Offset,
b"relativeTo" => CalendarProperty::RelativeTo,
b"action" => CalendarProperty::Action,
)
.or_else(|| {
if allow_patch && value.contains('/') {
CalendarProperty::Pointer(JsonPointer::parse(value)).into()
} else {
None
}
})
}
fn patch_or_prop(&self) -> &CalendarProperty {
if let CalendarProperty::Pointer(ptr) = self
&& let Some(JsonPointerItem::Key(Key::Property(prop))) = ptr.last()
{
prop
} else {
self
}
}
}
#[derive(Debug, Clone, Default)]
pub struct CalendarSetArguments {
pub on_destroy_remove_events: Option<bool>,
pub on_success_set_is_default: Option<MaybeIdReference<Id>>,
}
impl<'de> DeserializeArguments<'de> for CalendarSetArguments {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'de>,
{
hashify::fnc_map!(key.as_bytes(),
b"onDestroyRemoveEvents" => {
self.on_destroy_remove_events = map.next_value()?;
},
b"onSuccessSetIsDefault" => {
self.on_success_set_is_default = map.next_value()?;
},
_ => {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
);
Ok(())
}
}
impl FromStr for CalendarProperty {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
CalendarProperty::parse(s, false).ok_or(())
}
}
impl JmapObject for Calendar {
type Property = CalendarProperty;
type Element = CalendarValue;
type Id = Id;
type Filter = ();
type Comparator = ();
type GetArguments = ();
type SetArguments<'de> = CalendarSetArguments;
type QueryArguments = ();
type CopyArguments = ();
type ParseArguments = ();
const ID_PROPERTY: Self::Property = CalendarProperty::Id;
}
impl JmapSharedObject for Calendar {
type Right = CalendarRight;
const SHARE_WITH_PROPERTY: Self::Property = CalendarProperty::ShareWith;
}
impl From<Id> for CalendarProperty {
fn from(id: Id) -> Self {
CalendarProperty::IdValue(id)
}
}
impl TryFrom<CalendarProperty> for Id {
type Error = ();
fn try_from(value: CalendarProperty) -> Result<Self, Self::Error> {
if let CalendarProperty::IdValue(id) = value {
Ok(id)
} else {
Err(())
}
}
}
impl TryFrom<CalendarProperty> for CalendarRight {
type Error = ();
fn try_from(value: CalendarProperty) -> Result<Self, Self::Error> {
if let CalendarProperty::Rights(right) = value {
Ok(right)
} else {
Err(())
}
}
}
impl From<Id> for CalendarValue {
fn from(id: Id) -> Self {
CalendarValue::Id(id)
}
}
impl JmapObjectId for CalendarValue {
fn as_id(&self) -> Option<Id> {
if let CalendarValue::Id(id) = self {
Some(*id)
} else {
None
}
}
fn as_any_id(&self) -> Option<AnyId> {
if let CalendarValue::Id(id) = self {
Some(AnyId::Id(*id))
} else {
None
}
}
fn as_id_ref(&self) -> Option<&str> {
if let CalendarValue::IdReference(r) = self {
Some(r)
} else {
None
}
}
fn try_set_id(&mut self, new_id: AnyId) -> bool {
if let AnyId::Id(new_id) = new_id {
*self = CalendarValue::Id(new_id);
return true;
}
false
}
}
impl JmapRight for CalendarRight {
fn to_acl(&self) -> &'static [Acl] {
match self {
CalendarRight::MayReadFreeBusy => &[Acl::SchedulingReadFreeBusy],
CalendarRight::MayReadItems => &[Acl::Read, Acl::ReadItems],
CalendarRight::MayWriteAll => &[
Acl::Modify,
Acl::AddItems,
Acl::ModifyItems,
Acl::RemoveItems,
],
CalendarRight::MayWriteOwn => &[Acl::ModifyItemsOwn],
CalendarRight::MayUpdatePrivate => &[Acl::ModifyPrivateProperties],
CalendarRight::MayRSVP => &[Acl::ModifyRSVP],
CalendarRight::MayShare => &[Acl::Share],
CalendarRight::MayDelete => &[Acl::Delete, Acl::RemoveItems],
}
}
fn all_rights() -> &'static [Self] {
&[
CalendarRight::MayReadFreeBusy,
CalendarRight::MayReadItems,
CalendarRight::MayWriteAll,
CalendarRight::MayWriteOwn,
CalendarRight::MayUpdatePrivate,
CalendarRight::MayRSVP,
CalendarRight::MayShare,
CalendarRight::MayDelete,
]
}
}
impl From<CalendarRight> for CalendarProperty {
fn from(right: CalendarRight) -> Self {
CalendarProperty::Rights(right)
}
}
impl JmapObjectId for CalendarProperty {
fn as_id(&self) -> Option<Id> {
if let CalendarProperty::IdValue(id) = self {
Some(*id)
} else {
None
}
}
fn as_any_id(&self) -> Option<AnyId> {
if let CalendarProperty::IdValue(id) = self {
Some(AnyId::Id(*id))
} else {
None
}
}
fn as_id_ref(&self) -> Option<&str> {
None
}
fn try_set_id(&mut self, new_id: AnyId) -> bool {
if let AnyId::Id(new_id) = new_id {
*self = CalendarProperty::IdValue(new_id);
return true;
}
false
}
}
impl Display for CalendarProperty {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_cow())
}
}
@@ -0,0 +1,386 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
object::{AnyId, JmapObject, JmapObjectId},
request::{MaybeInvalid, deserialize::DeserializeArguments},
};
use calcard::{
common::timezone::Tz,
jscalendar::{JSCalendarDateTime, JSCalendarProperty, JSCalendarValue},
};
use jmap_tools::{JsonPointerItem, Key};
use mail_parser::DateTime;
use std::{borrow::Cow, str::FromStr};
use types::{blob::BlobId, id::Id};
#[derive(Debug, Clone, Default)]
pub struct CalendarEvent;
impl JmapObject for CalendarEvent {
type Property = JSCalendarProperty<Id>;
type Element = JSCalendarValue<Id, BlobId>;
type Id = Id;
type Filter = CalendarEventFilter;
type Comparator = CalendarEventComparator;
type GetArguments = CalendarEventGetArguments;
type SetArguments<'de> = CalendarEventSetArguments;
type QueryArguments = CalendarEventQueryArguments;
type CopyArguments = ();
type ParseArguments = ();
const ID_PROPERTY: Self::Property = JSCalendarProperty::Id;
}
impl JmapObjectId for JSCalendarValue<Id, BlobId> {
fn as_id(&self) -> Option<Id> {
if let JSCalendarValue::Id(id) = self {
Some(*id)
} else {
None
}
}
fn as_any_id(&self) -> Option<AnyId> {
match self {
JSCalendarValue::Id(id) => Some(AnyId::Id(*id)),
JSCalendarValue::BlobId(blob_id) => Some(AnyId::BlobId(blob_id.clone())),
_ => None,
}
}
fn as_id_ref(&self) -> Option<&str> {
match self {
JSCalendarValue::IdReference(r) => Some(r),
_ => None,
}
}
fn try_set_id(&mut self, new_id: AnyId) -> bool {
if let AnyId::Id(id) = new_id {
*self = JSCalendarValue::Id(id);
true
} else {
false
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CalendarEventFilter {
InCalendar(MaybeInvalid<Id>),
After(JSCalendarDateTime),
Before(JSCalendarDateTime),
Text(String),
Title(String),
Description(String),
Location(String),
Owner(String),
Attendee(String),
Uid(String),
_T(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CalendarEventComparator {
Start,
Uid,
RecurrenceId,
Created,
Updated,
_T(String),
}
#[derive(Debug, Clone, Default)]
pub struct CalendarEventGetArguments {
pub recurrence_overrides_before: Option<JSCalendarDateTime>,
pub recurrence_overrides_after: Option<JSCalendarDateTime>,
pub reduce_participants: Option<bool>,
pub time_zone: Option<Tz>,
}
#[derive(Debug, Clone, Default)]
pub struct CalendarEventSetArguments {
pub send_scheduling_messages: Option<bool>,
}
#[derive(Debug, Clone, Default)]
pub struct CalendarEventQueryArguments {
pub expand_recurrences: Option<bool>,
pub time_zone: Option<Tz>,
}
impl<'de> DeserializeArguments<'de> for CalendarEventFilter {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'de>,
{
hashify::fnc_map!(key.as_bytes(),
b"inCalendar" => {
*self = CalendarEventFilter::InCalendar(map.next_value()?);
},
b"after" => {
*self = CalendarEventFilter::After(map.next_value::<LocalTime>()?.0);
},
b"before" => {
*self = CalendarEventFilter::Before(map.next_value::<LocalTime>()?.0);
},
b"text" => {
*self = CalendarEventFilter::Text(map.next_value::<Cow<str>>()?.to_lowercase());
},
b"title" => {
*self = CalendarEventFilter::Title(map.next_value::<Cow<str>>()?.to_lowercase());
},
b"description" => {
*self = CalendarEventFilter::Description(map.next_value::<Cow<str>>()?.to_lowercase());
},
b"location" => {
*self = CalendarEventFilter::Location(map.next_value::<Cow<str>>()?.to_lowercase());
},
b"owner" => {
*self = CalendarEventFilter::Owner(map.next_value::<Cow<str>>()?.to_lowercase());
},
b"attendee" => {
*self = CalendarEventFilter::Attendee(map.next_value::<Cow<str>>()?.to_lowercase());
},
b"uid" => {
*self = CalendarEventFilter::Uid(map.next_value()?);
},
_ => {
*self = CalendarEventFilter::_T(key.to_string());
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
);
Ok(())
}
}
impl<'de> DeserializeArguments<'de> for CalendarEventComparator {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'de>,
{
if key == "property" {
let value = map.next_value::<Cow<str>>()?;
hashify::fnc_map!(value.as_bytes(),
b"start" => {
*self = CalendarEventComparator::Start;
},
b"uid" => {
*self = CalendarEventComparator::Uid;
},
b"recurrenceId" => {
*self = CalendarEventComparator::RecurrenceId;
},
b"created" => {
*self = CalendarEventComparator::Created;
},
b"updated" => {
*self = CalendarEventComparator::Updated;
},
_ => {
*self = CalendarEventComparator::_T(value.to_string());
}
);
} else {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
Ok(())
}
}
impl<'de> DeserializeArguments<'de> for CalendarEventGetArguments {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'de>,
{
hashify::fnc_map!(key.as_bytes(),
b"recurrenceOverridesBefore" => {
self.recurrence_overrides_before = map.next_value::<Option<LocalTime>>()?.map(|lt| lt.0)
},
b"recurrenceOverridesAfter" => {
self.recurrence_overrides_after = map.next_value::<Option<LocalTime>>()?.map(|lt| lt.0);
},
b"reduceParticipants" => {
self.reduce_participants = map.next_value()?;
},
b"timeZone" => {
self.time_zone = map.next_value::<Option<&str>>()?.and_then(|s| Tz::from_str(s).ok());
},
_ => {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
);
Ok(())
}
}
impl<'de> DeserializeArguments<'de> for CalendarEventSetArguments {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'de>,
{
hashify::fnc_map!(key.as_bytes(),
b"sendSchedulingMessages" => {
self.send_scheduling_messages = map.next_value()?;
},
_ => {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
);
Ok(())
}
}
impl<'de> DeserializeArguments<'de> for CalendarEventQueryArguments {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'de>,
{
hashify::fnc_map!(key.as_bytes(),
b"expandRecurrences" => {
self.expand_recurrences = map.next_value()?;
},
b"timeZone" => {
self.time_zone = map.next_value::<Option<&str>>()?.and_then(|s| Tz::from_str(s).ok());
},
_ => {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
);
Ok(())
}
}
impl CalendarEventFilter {
pub fn into_string(self) -> Cow<'static, str> {
match self {
CalendarEventFilter::InCalendar(_) => "inCalendar",
CalendarEventFilter::After(_) => "after",
CalendarEventFilter::Before(_) => "before",
CalendarEventFilter::Text(_) => "text",
CalendarEventFilter::Title(_) => "title",
CalendarEventFilter::Description(_) => "description",
CalendarEventFilter::Location(_) => "location",
CalendarEventFilter::Owner(_) => "owner",
CalendarEventFilter::Attendee(_) => "attendee",
CalendarEventFilter::Uid(_) => "uid",
CalendarEventFilter::_T(s) => return Cow::Owned(s),
}
.into()
}
}
impl CalendarEventComparator {
pub fn into_string(self) -> Cow<'static, str> {
match self {
CalendarEventComparator::Start => "start",
CalendarEventComparator::Uid => "uid",
CalendarEventComparator::RecurrenceId => "recurrenceId",
CalendarEventComparator::Created => "created",
CalendarEventComparator::Updated => "updated",
CalendarEventComparator::_T(s) => return Cow::Owned(s),
}
.into()
}
}
impl Default for CalendarEventFilter {
fn default() -> Self {
CalendarEventFilter::_T(String::new())
}
}
impl Default for CalendarEventComparator {
fn default() -> Self {
CalendarEventComparator::_T(String::new())
}
}
struct LocalTime(JSCalendarDateTime);
impl<'de> serde::Deserialize<'de> for LocalTime {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = <&str>::deserialize(deserializer)?;
if let Some(dt) = DateTime::parse_rfc3339(value) {
Ok(LocalTime(JSCalendarDateTime {
timestamp: dt.to_timestamp_local(),
is_local: true,
}))
} else {
Err(serde::de::Error::custom(format!(
"Invalid datetime: {}",
value
)))
}
}
}
impl JmapObjectId for JSCalendarProperty<Id> {
fn as_id(&self) -> Option<Id> {
if let JSCalendarProperty::IdValue(id) = self {
Some(*id)
} else {
None
}
}
fn as_any_id(&self) -> Option<AnyId> {
if let JSCalendarProperty::IdValue(id) = self {
Some(AnyId::Id(*id))
} else {
None
}
}
fn as_id_ref(&self) -> Option<&str> {
match self {
JSCalendarProperty::IdReference(r) => Some(r),
JSCalendarProperty::Pointer(value) => {
let value = value.as_slice();
match (value.first(), value.get(1)) {
(
Some(JsonPointerItem::Key(Key::Property(JSCalendarProperty::CalendarIds))),
Some(JsonPointerItem::Key(Key::Property(JSCalendarProperty::IdReference(
r,
)))),
) => Some(r),
_ => None,
}
}
_ => None,
}
}
fn try_set_id(&mut self, new_id: AnyId) -> bool {
if let AnyId::Id(id) = new_id {
if let JSCalendarProperty::Pointer(value) = self {
let value = value.as_mut_slice();
if let Some(value) = value.get_mut(1) {
*value = JsonPointerItem::Key(Key::Property(JSCalendarProperty::IdValue(id)));
return true;
}
} else {
*self = JSCalendarProperty::IdValue(id);
return true;
}
}
false
}
}
@@ -0,0 +1,412 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
object::{AnyId, JmapObject, JmapObjectId},
request::{MaybeInvalid, deserialize::DeserializeArguments},
types::{date::UTCDate, state::State},
};
use calcard::jscalendar::JSCalendar;
use jmap_tools::{Element, Key, Property};
use serde::Serialize;
use std::{borrow::Cow, fmt::Display, str::FromStr};
use types::{blob::BlobId, id::Id};
#[derive(Debug, Clone, Default)]
pub struct CalendarEventNotification;
#[derive(Debug, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct CalendarEventNotificationObject {
pub id: Id,
#[serde(skip_serializing_if = "Option::is_none")]
pub created: Option<UTCDate>,
#[serde(skip_serializing_if = "Option::is_none")]
pub changed_by: Option<PersonObject>,
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "type")]
pub notification_type: Option<CalendarEventNotificationType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub calendar_event_id: Option<Id>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_draft: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub event: Option<JSCalendar<'static, Id, BlobId>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub event_patch: Option<JSCalendar<'static, Id, BlobId>>,
}
#[derive(Debug, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct PersonObject {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub email: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub principal_id: Option<Id>,
#[serde(skip_serializing_if = "Option::is_none")]
pub calendar_address: Option<String>,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct CalendarEventNotificationGetResponse {
#[serde(rename = "accountId")]
#[serde(skip_serializing_if = "Option::is_none")]
pub account_id: Option<Id>,
#[serde(skip_serializing_if = "Option::is_none")]
pub state: Option<State>,
pub list: Vec<CalendarEventNotificationObject>,
#[serde(rename = "notFound")]
pub not_found: Vec<crate::request::MaybeInvalid<Id>>,
}
impl CalendarEventNotificationGetResponse {
pub fn push_not_found(&mut self, id: Id) {
self.not_found.push(crate::request::MaybeInvalid::Value(id));
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum CalendarEventNotificationProperty {
Id,
Created,
ChangedBy,
Comment,
Type,
CalendarEventId,
IsDraft,
Event,
EventPatch,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum CalendarEventNotificationValue {
Id(Id),
Date(UTCDate),
Type(CalendarEventNotificationType),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum CalendarEventNotificationType {
Created,
Updated,
Destroyed,
}
impl Property for CalendarEventNotificationProperty {
fn try_parse(_: Option<&Key<'_, Self>>, value: &str) -> Option<Self> {
CalendarEventNotificationProperty::parse(value)
}
fn to_cow(&self) -> Cow<'static, str> {
match self {
CalendarEventNotificationProperty::Id => "id",
CalendarEventNotificationProperty::Created => "created",
CalendarEventNotificationProperty::ChangedBy => "changedBy",
CalendarEventNotificationProperty::Comment => "comment",
CalendarEventNotificationProperty::Type => "type",
CalendarEventNotificationProperty::CalendarEventId => "calendarEventId",
CalendarEventNotificationProperty::IsDraft => "isDraft",
CalendarEventNotificationProperty::Event => "event",
CalendarEventNotificationProperty::EventPatch => "eventPatch",
}
.into()
}
}
impl Element for CalendarEventNotificationValue {
type Property = CalendarEventNotificationProperty;
fn try_parse<P>(key: &Key<'_, Self::Property>, value: &str) -> Option<Self> {
if let Key::Property(prop) = key {
match prop {
CalendarEventNotificationProperty::Id
| CalendarEventNotificationProperty::CalendarEventId => Id::from_str(value)
.ok()
.map(CalendarEventNotificationValue::Id),
CalendarEventNotificationProperty::Created => UTCDate::from_str(value)
.ok()
.map(CalendarEventNotificationValue::Date),
CalendarEventNotificationProperty::Type => {
CalendarEventNotificationType::parse(value)
.map(CalendarEventNotificationValue::Type)
}
_ => None,
}
} else {
None
}
}
fn to_cow(&self) -> Cow<'static, str> {
match self {
CalendarEventNotificationValue::Id(id) => id.to_string().into(),
CalendarEventNotificationValue::Date(date) => date.to_string().into(),
CalendarEventNotificationValue::Type(t) => t.as_str().into(),
}
}
}
impl CalendarEventNotificationType {
fn parse(value: &str) -> Option<Self> {
hashify::tiny_map!(value.as_bytes(),
b"created" => CalendarEventNotificationType::Created,
b"updated" => CalendarEventNotificationType::Updated,
b"destroyed" => CalendarEventNotificationType::Destroyed,
)
}
pub fn as_str(&self) -> &'static str {
match self {
CalendarEventNotificationType::Created => "created",
CalendarEventNotificationType::Updated => "updated",
CalendarEventNotificationType::Destroyed => "destroyed",
}
}
}
impl CalendarEventNotificationProperty {
fn parse(value: &str) -> Option<Self> {
hashify::tiny_map!(value.as_bytes(),
b"id" => CalendarEventNotificationProperty::Id,
b"created" => CalendarEventNotificationProperty::Created,
b"changedBy" => CalendarEventNotificationProperty::ChangedBy,
b"comment" => CalendarEventNotificationProperty::Comment,
b"type" => CalendarEventNotificationProperty::Type,
b"calendarEventId" => CalendarEventNotificationProperty::CalendarEventId,
b"isDraft" => CalendarEventNotificationProperty::IsDraft,
b"event" => CalendarEventNotificationProperty::Event,
b"eventPatch" => CalendarEventNotificationProperty::EventPatch
)
}
}
impl FromStr for CalendarEventNotificationProperty {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
CalendarEventNotificationProperty::parse(s).ok_or(())
}
}
impl JmapObject for CalendarEventNotification {
type Property = CalendarEventNotificationProperty;
type Element = CalendarEventNotificationValue;
type Id = Id;
type Filter = CalendarEventNotificationFilter;
type Comparator = CalendarEventNotificationComparator;
type GetArguments = ();
type SetArguments<'de> = ();
type QueryArguments = ();
type CopyArguments = ();
type ParseArguments = ();
const ID_PROPERTY: Self::Property = CalendarEventNotificationProperty::Id;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CalendarEventNotificationFilter {
After(UTCDate),
Before(UTCDate),
Type(CalendarEventNotificationType),
CalendarEventIds(Vec<MaybeInvalid<Id>>),
_T(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CalendarEventNotificationComparator {
Created,
_T(String),
}
impl<'de> DeserializeArguments<'de> for CalendarEventNotificationFilter {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'de>,
{
hashify::fnc_map!(key.as_bytes(),
b"after" => {
*self = CalendarEventNotificationFilter::After(map.next_value()?);
},
b"before" => {
*self = CalendarEventNotificationFilter::Before(map.next_value()?);
},
b"type" => {
*self = CalendarEventNotificationFilter::Type(map.next_value()?);
},
b"calendarEventIds" => {
*self = CalendarEventNotificationFilter::CalendarEventIds(map.next_value()?);
},
_ => {
*self = CalendarEventNotificationFilter::_T(key.to_string());
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
);
Ok(())
}
}
impl<'de> DeserializeArguments<'de> for CalendarEventNotificationComparator {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'de>,
{
if key == "property" {
let value = map.next_value::<Cow<str>>()?;
hashify::fnc_map!(value.as_bytes(),
b"created" => {
*self = CalendarEventNotificationComparator::Created;
},
_ => {
*self = CalendarEventNotificationComparator::_T(value.to_string());
}
);
} else {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
Ok(())
}
}
impl<'de> serde::Deserialize<'de> for CalendarEventNotificationType {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
CalendarEventNotificationType::parse(<&str>::deserialize(deserializer)?)
.ok_or_else(|| serde::de::Error::custom("invalid CalendarEventNotificationType"))
}
}
impl CalendarEventNotificationFilter {
pub fn into_string(self) -> Cow<'static, str> {
match self {
CalendarEventNotificationFilter::After(_) => "after",
CalendarEventNotificationFilter::Before(_) => "before",
CalendarEventNotificationFilter::Type(_) => "type",
CalendarEventNotificationFilter::CalendarEventIds(_) => "calendarEventIds",
CalendarEventNotificationFilter::_T(s) => return Cow::Owned(s),
}
.into()
}
}
impl CalendarEventNotificationComparator {
pub fn into_string(self) -> Cow<'static, str> {
match self {
CalendarEventNotificationComparator::Created => "created",
CalendarEventNotificationComparator::_T(s) => return Cow::Owned(s),
}
.into()
}
}
impl Default for CalendarEventNotificationFilter {
fn default() -> Self {
CalendarEventNotificationFilter::_T(String::new())
}
}
impl Default for CalendarEventNotificationComparator {
fn default() -> Self {
CalendarEventNotificationComparator::_T(String::new())
}
}
impl TryFrom<CalendarEventNotificationProperty> for Id {
type Error = ();
fn try_from(_: CalendarEventNotificationProperty) -> Result<Self, Self::Error> {
Err(())
}
}
impl From<Id> for CalendarEventNotificationValue {
fn from(id: Id) -> Self {
CalendarEventNotificationValue::Id(id)
}
}
impl JmapObjectId for CalendarEventNotificationValue {
fn as_id(&self) -> Option<Id> {
if let CalendarEventNotificationValue::Id(id) = self {
Some(*id)
} else {
None
}
}
fn as_any_id(&self) -> Option<AnyId> {
if let CalendarEventNotificationValue::Id(id) = self {
Some(AnyId::Id(*id))
} else {
None
}
}
fn as_id_ref(&self) -> Option<&str> {
None
}
fn try_set_id(&mut self, _: AnyId) -> bool {
false
}
}
impl JmapObjectId for CalendarEventNotificationProperty {
fn as_id(&self) -> Option<Id> {
None
}
fn as_any_id(&self) -> Option<AnyId> {
None
}
fn as_id_ref(&self) -> Option<&str> {
None
}
fn try_set_id(&mut self, _: AnyId) -> bool {
false
}
}
impl serde::Serialize for CalendarEventNotificationType {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl Display for CalendarEventNotificationProperty {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_cow())
}
}
+334
View File
@@ -0,0 +1,334 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
object::{AnyId, JmapObject, JmapObjectId},
request::{MaybeInvalid, deserialize::DeserializeArguments},
types::date::UTCDate,
};
use calcard::jscontact::{JSContactProperty, JSContactValue};
use jmap_tools::{JsonPointerItem, Key};
use std::borrow::Cow;
use types::{blob::BlobId, id::Id};
#[derive(Debug, Clone, Default)]
pub struct ContactCard;
impl JmapObject for ContactCard {
type Property = JSContactProperty<Id>;
type Element = JSContactValue<Id, BlobId>;
type Id = Id;
type Filter = ContactCardFilter;
type Comparator = ContactCardComparator;
type GetArguments = ();
type SetArguments<'de> = ();
type QueryArguments = ();
type CopyArguments = ();
type ParseArguments = ();
const ID_PROPERTY: Self::Property = JSContactProperty::Id;
}
impl JmapObjectId for JSContactValue<Id, BlobId> {
fn as_id(&self) -> Option<Id> {
if let JSContactValue::Id(id) = self {
Some(*id)
} else {
None
}
}
fn as_any_id(&self) -> Option<AnyId> {
match self {
JSContactValue::Id(id) => Some(AnyId::Id(*id)),
JSContactValue::BlobId(id) => Some(AnyId::BlobId(id.clone())),
_ => None,
}
}
fn as_id_ref(&self) -> Option<&str> {
match self {
JSContactValue::IdReference(r) => Some(r),
_ => None,
}
}
fn try_set_id(&mut self, new_id: AnyId) -> bool {
match new_id {
AnyId::Id(id) => {
*self = JSContactValue::Id(id);
}
AnyId::BlobId(id) => {
*self = JSContactValue::BlobId(id);
}
}
true
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ContactCardFilter {
InAddressBook(MaybeInvalid<Id>),
Uid(String),
HasMember(String),
Kind(String),
CreatedBefore(UTCDate),
CreatedAfter(UTCDate),
UpdatedBefore(UTCDate),
UpdatedAfter(UTCDate),
Text(String),
Name(String),
NameGiven(String),
NameSurname(String),
NameSurname2(String),
Nickname(String),
Organization(String),
Email(String),
Phone(String),
OnlineService(String),
Address(String),
Note(String),
_T(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ContactCardComparator {
Created,
Updated,
NameGiven,
NameSurname,
NameSurname2,
_T(String),
}
impl<'de> DeserializeArguments<'de> for ContactCardFilter {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'de>,
{
hashify::fnc_map!(key.as_bytes(),
b"inAddressBook" => {
*self = ContactCardFilter::InAddressBook(map.next_value()?);
},
b"uid" => {
*self = ContactCardFilter::Uid(map.next_value()?);
},
b"hasMember" => {
*self = ContactCardFilter::HasMember(map.next_value()?);
},
b"kind" => {
*self = ContactCardFilter::Kind(map.next_value()?);
},
b"createdBefore" => {
*self = ContactCardFilter::CreatedBefore(map.next_value()?);
},
b"createdAfter" => {
*self = ContactCardFilter::CreatedAfter(map.next_value()?);
},
b"updatedBefore" => {
*self = ContactCardFilter::UpdatedBefore(map.next_value()?);
},
b"updatedAfter" => {
*self = ContactCardFilter::UpdatedAfter(map.next_value()?);
},
b"text" => {
*self = ContactCardFilter::Text(map.next_value::<Cow<str>>()?.to_lowercase());
},
b"name" => {
*self = ContactCardFilter::Name(map.next_value::<Cow<str>>()?.to_lowercase());
},
b"name/given" => {
*self = ContactCardFilter::NameGiven(map.next_value::<Cow<str>>()?.to_lowercase());
},
b"name/surname" => {
*self = ContactCardFilter::NameSurname(map.next_value::<Cow<str>>()?.to_lowercase());
},
b"name/surname2" => {
*self = ContactCardFilter::NameSurname2(map.next_value::<Cow<str>>()?.to_lowercase());
},
b"nickname" => {
*self = ContactCardFilter::Nickname(map.next_value::<Cow<str>>()?.to_lowercase());
},
b"organization" => {
*self = ContactCardFilter::Organization(map.next_value::<Cow<str>>()?.to_lowercase());
},
b"email" => {
*self = ContactCardFilter::Email(map.next_value()?);
},
b"phone" => {
*self = ContactCardFilter::Phone(map.next_value::<Cow<str>>()?.to_lowercase());
},
b"onlineService" => {
*self = ContactCardFilter::OnlineService(map.next_value::<Cow<str>>()?.to_lowercase());
},
b"address" => {
*self = ContactCardFilter::Address(map.next_value::<Cow<str>>()?.to_lowercase());
},
b"note" => {
*self = ContactCardFilter::Note(map.next_value::<Cow<str>>()?.to_lowercase());
},
_ => {
*self = ContactCardFilter::_T(key.to_string());
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
);
Ok(())
}
}
impl<'de> DeserializeArguments<'de> for ContactCardComparator {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'de>,
{
if key == "property" {
let value = map.next_value::<Cow<str>>()?;
hashify::fnc_map!(value.as_bytes(),
b"created" => {
*self = ContactCardComparator::Created;
},
b"updated" => {
*self = ContactCardComparator::Updated;
},
b"name/given" => {
*self = ContactCardComparator::NameGiven;
},
b"name/surname" => {
*self = ContactCardComparator::NameSurname;
},
b"name/surname2" => {
*self = ContactCardComparator::NameSurname2;
},
_ => {
*self = ContactCardComparator::_T(value.to_string());
}
);
} else {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
Ok(())
}
}
impl ContactCardFilter {
pub fn into_string(self) -> Cow<'static, str> {
match self {
ContactCardFilter::InAddressBook(_) => "inAddressBook",
ContactCardFilter::Uid(_) => "uid",
ContactCardFilter::HasMember(_) => "hasMember",
ContactCardFilter::Kind(_) => "kind",
ContactCardFilter::CreatedBefore(_) => "createdBefore",
ContactCardFilter::CreatedAfter(_) => "createdAfter",
ContactCardFilter::UpdatedBefore(_) => "updatedBefore",
ContactCardFilter::UpdatedAfter(_) => "updatedAfter",
ContactCardFilter::Text(_) => "text",
ContactCardFilter::Name(_) => "name",
ContactCardFilter::NameGiven(_) => "name/given",
ContactCardFilter::NameSurname(_) => "name/surname",
ContactCardFilter::NameSurname2(_) => "name/surname2",
ContactCardFilter::Nickname(_) => "nickname",
ContactCardFilter::Organization(_) => "organization",
ContactCardFilter::Email(_) => "email",
ContactCardFilter::Phone(_) => "phone",
ContactCardFilter::OnlineService(_) => "onlineService",
ContactCardFilter::Address(_) => "address",
ContactCardFilter::Note(_) => "note",
ContactCardFilter::_T(s) => return Cow::Owned(s),
}
.into()
}
}
impl ContactCardComparator {
pub fn into_string(self) -> Cow<'static, str> {
match self {
ContactCardComparator::Created => "created",
ContactCardComparator::Updated => "updated",
ContactCardComparator::NameGiven => "name/given",
ContactCardComparator::NameSurname => "name/surname",
ContactCardComparator::NameSurname2 => "name/surname2",
ContactCardComparator::_T(s) => return Cow::Owned(s),
}
.into()
}
}
impl Default for ContactCardFilter {
fn default() -> Self {
ContactCardFilter::_T(String::new())
}
}
impl Default for ContactCardComparator {
fn default() -> Self {
ContactCardComparator::_T(String::new())
}
}
impl JmapObjectId for JSContactProperty<Id> {
fn as_id(&self) -> Option<Id> {
if let JSContactProperty::IdValue(id) = self {
Some(*id)
} else {
None
}
}
fn as_any_id(&self) -> Option<AnyId> {
if let JSContactProperty::IdValue(id) = self {
Some(AnyId::Id(*id))
} else {
None
}
}
fn as_id_ref(&self) -> Option<&str> {
match self {
JSContactProperty::IdReference(r) => Some(r),
JSContactProperty::Pointer(value) => {
let value = value.as_slice();
match (value.first(), value.get(1)) {
(
Some(JsonPointerItem::Key(Key::Property(
JSContactProperty::AddressBookIds,
))),
Some(JsonPointerItem::Key(Key::Property(JSContactProperty::IdReference(
r,
)))),
) => Some(r),
_ => None,
}
}
_ => None,
}
}
fn try_set_id(&mut self, new_id: AnyId) -> bool {
if let AnyId::Id(id) = new_id {
if let JSContactProperty::Pointer(value) = self {
let value = value.as_mut_slice();
if let Some(value) = value.get_mut(1) {
*value = JsonPointerItem::Key(Key::Property(JSContactProperty::IdValue(id)));
return true;
}
} else {
*self = JSContactProperty::IdValue(id);
return true;
}
}
false
}
}
+967
View File
@@ -0,0 +1,967 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
method::query::{Comparator, Filter},
object::{AnyId, JmapObject, JmapObjectId, MaybeReference, parse_ref},
request::{MaybeInvalid, deserialize::DeserializeArguments},
types::date::UTCDate,
};
use jmap_tools::{Element, JsonPointer, JsonPointerItem, Key, Property};
use mail_parser::HeaderName;
use serde::Serialize;
use std::{borrow::Cow, fmt::Display, str::FromStr};
use types::{blob::BlobId, id::Id, keyword::Keyword};
#[derive(Debug, Clone, Default)]
pub struct Email;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum EmailProperty {
// Metadata
Id,
BlobId,
ThreadId,
MailboxIds,
Keywords,
Size,
ReceivedAt,
// Address
Name,
Email,
// GroupedAddresses
Addresses,
// Header Fields Properties
Value,
Header(HeaderProperty),
// Convenience properties
MessageId,
InReplyTo,
References,
Sender,
From,
To,
Cc,
Bcc,
ReplyTo,
Subject,
SentAt,
// Body Parts
TextBody,
HtmlBody,
Attachments,
PartId,
Headers,
Type,
Charset,
Disposition,
Cid,
Language,
Location,
SubParts,
BodyStructure,
BodyValues,
IsEncodingProblem,
IsTruncated,
HasAttachment,
Preview,
// Other
Keyword(Keyword),
IdValue(Id),
IdReference(String),
Pointer(JsonPointer<EmailProperty>),
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HeaderProperty {
pub form: HeaderForm,
pub header: String,
pub all: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum HeaderForm {
Raw,
Text,
Addresses,
GroupedAddresses,
MessageIds,
Date,
URLs,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum EmailValue {
Id(Id),
Date(UTCDate),
BlobId(BlobId),
IdReference(String),
}
impl Property for EmailProperty {
fn try_parse(key: Option<&Key<'_, Self>>, value: &str) -> Option<Self> {
let allow_patch = key.is_none();
if let Some(Key::Property(key)) = key {
match key.patch_or_prop() {
EmailProperty::Keywords => EmailProperty::Keyword(Keyword::parse(value)).into(),
EmailProperty::MailboxIds => match parse_ref(value) {
MaybeReference::Value(v) => Some(EmailProperty::IdValue(v)),
MaybeReference::Reference(v) => Some(EmailProperty::IdReference(v)),
MaybeReference::ParseError => None,
},
_ => EmailProperty::parse(value, allow_patch),
}
} else {
EmailProperty::parse(value, allow_patch)
}
}
fn to_cow(&self) -> Cow<'static, str> {
match self {
EmailProperty::Attachments => "attachments",
EmailProperty::Bcc => "bcc",
EmailProperty::BlobId => "blobId",
EmailProperty::BodyStructure => "bodyStructure",
EmailProperty::BodyValues => "bodyValues",
EmailProperty::Cc => "cc",
EmailProperty::Charset => "charset",
EmailProperty::Cid => "cid",
EmailProperty::Disposition => "disposition",
EmailProperty::Email => "email",
EmailProperty::From => "from",
EmailProperty::HasAttachment => "hasAttachment",
EmailProperty::Headers => "headers",
EmailProperty::HtmlBody => "htmlBody",
EmailProperty::Id => "id",
EmailProperty::InReplyTo => "inReplyTo",
EmailProperty::Keywords => "keywords",
EmailProperty::Language => "language",
EmailProperty::Location => "location",
EmailProperty::MailboxIds => "mailboxIds",
EmailProperty::MessageId => "messageId",
EmailProperty::Name => "name",
EmailProperty::PartId => "partId",
EmailProperty::Preview => "preview",
EmailProperty::ReceivedAt => "receivedAt",
EmailProperty::References => "references",
EmailProperty::ReplyTo => "replyTo",
EmailProperty::Sender => "sender",
EmailProperty::SentAt => "sentAt",
EmailProperty::Size => "size",
EmailProperty::Subject => "subject",
EmailProperty::SubParts => "subParts",
EmailProperty::TextBody => "textBody",
EmailProperty::ThreadId => "threadId",
EmailProperty::To => "to",
EmailProperty::Type => "type",
EmailProperty::Addresses => "addresses",
EmailProperty::Value => "value",
EmailProperty::IsEncodingProblem => "isEncodingProblem",
EmailProperty::IsTruncated => "isTruncated",
EmailProperty::Header(header) => return header.to_string().into(),
EmailProperty::Keyword(keyword) => return keyword.to_string().into(),
EmailProperty::IdValue(id) => return id.to_string().into(),
EmailProperty::Pointer(json_pointer) => return json_pointer.to_string().into(),
EmailProperty::IdReference(r) => return format!("#{r}").into(),
}
.into()
}
}
impl Element for EmailValue {
type Property = EmailProperty;
fn try_parse<P>(key: &Key<'_, Self::Property>, value: &str) -> Option<Self> {
if let Key::Property(prop) = key {
match prop.patch_or_prop() {
EmailProperty::Id | EmailProperty::ThreadId | EmailProperty::MailboxIds => {
match parse_ref(value) {
MaybeReference::Value(v) => Some(EmailValue::Id(v)),
MaybeReference::Reference(v) => Some(EmailValue::IdReference(v)),
MaybeReference::ParseError => None,
}
}
EmailProperty::BlobId => match parse_ref(value) {
MaybeReference::Value(v) => Some(EmailValue::BlobId(v)),
MaybeReference::Reference(v) => Some(EmailValue::IdReference(v)),
MaybeReference::ParseError => None,
},
EmailProperty::Header(HeaderProperty {
form: HeaderForm::Date,
..
})
| EmailProperty::ReceivedAt
| EmailProperty::SentAt => UTCDate::from_str(value).ok().map(EmailValue::Date),
_ => None,
}
} else {
None
}
}
fn to_cow(&self) -> Cow<'static, str> {
match self {
EmailValue::Id(id) => id.to_string().into(),
EmailValue::Date(utcdate) => utcdate.to_string().into(),
EmailValue::BlobId(blob_id) => blob_id.to_string().into(),
EmailValue::IdReference(r) => format!("#{r}").into(),
}
}
}
impl EmailProperty {
fn parse(value: &str, allow_patch: bool) -> Option<Self> {
hashify::tiny_map!(value.as_bytes(),
"id" => EmailProperty::Id,
"blobId" => EmailProperty::BlobId,
"threadId" => EmailProperty::ThreadId,
"mailboxIds" => EmailProperty::MailboxIds,
"keywords" => EmailProperty::Keywords,
"size" => EmailProperty::Size,
"receivedAt" => EmailProperty::ReceivedAt,
"name" => EmailProperty::Name,
"email" => EmailProperty::Email,
"addresses" => EmailProperty::Addresses,
"value" => EmailProperty::Value,
"messageId" => EmailProperty::MessageId,
"inReplyTo" => EmailProperty::InReplyTo,
"references" => EmailProperty::References,
"sender" => EmailProperty::Sender,
"from" => EmailProperty::From,
"to" => EmailProperty::To,
"cc" => EmailProperty::Cc,
"bcc" => EmailProperty::Bcc,
"replyTo" => EmailProperty::ReplyTo,
"subject" => EmailProperty::Subject,
"sentAt" => EmailProperty::SentAt,
"textBody" => EmailProperty::TextBody,
"htmlBody" => EmailProperty::HtmlBody,
"attachments" => EmailProperty::Attachments,
"partId" => EmailProperty::PartId,
"headers" => EmailProperty::Headers,
"type" => EmailProperty::Type,
"charset" => EmailProperty::Charset,
"disposition" => EmailProperty::Disposition,
"cid" => EmailProperty::Cid,
"language" => EmailProperty::Language,
"location" => EmailProperty::Location,
"subParts" => EmailProperty::SubParts,
"bodyStructure" => EmailProperty::BodyStructure,
"bodyValues" => EmailProperty::BodyValues,
"isEncodingProblem" => EmailProperty::IsEncodingProblem,
"isTruncated" => EmailProperty::IsTruncated,
"hasAttachment" => EmailProperty::HasAttachment,
"preview" => EmailProperty::Preview
)
.or_else(|| {
if let Some(header) = value.strip_prefix("header:") {
HeaderProperty::parse(header).map(EmailProperty::Header)
} else if allow_patch && value.contains('/') {
EmailProperty::Pointer(JsonPointer::parse(value)).into()
} else {
None
}
})
}
fn patch_or_prop(&self) -> &EmailProperty {
if let EmailProperty::Pointer(ptr) = self
&& let Some(JsonPointerItem::Key(Key::Property(prop))) = ptr.last()
{
prop
} else {
self
}
}
pub fn as_rfc_header(&self) -> HeaderName<'static> {
match self {
EmailProperty::MessageId => HeaderName::MessageId,
EmailProperty::InReplyTo => HeaderName::InReplyTo,
EmailProperty::References => HeaderName::References,
EmailProperty::Sender => HeaderName::Sender,
EmailProperty::From => HeaderName::From,
EmailProperty::To => HeaderName::To,
EmailProperty::Cc => HeaderName::Cc,
EmailProperty::Bcc => HeaderName::Bcc,
EmailProperty::ReplyTo => HeaderName::ReplyTo,
EmailProperty::Subject => HeaderName::Subject,
EmailProperty::SentAt => HeaderName::Date,
_ => unreachable!(),
}
}
pub fn try_into_id(self) -> Option<Id> {
match self {
EmailProperty::IdValue(id) => Some(id),
_ => None,
}
}
pub fn try_into_keyword(self) -> Option<Keyword> {
match self {
EmailProperty::Keyword(keyword) => Some(keyword),
_ => None,
}
}
}
impl HeaderProperty {
fn parse(value: &str) -> Option<Self> {
let mut result = HeaderProperty {
form: HeaderForm::Raw,
header: String::new(),
all: false,
};
for (pos, value) in value.split(':').enumerate() {
match pos {
0 => {
result.header = value.to_string();
}
1 => {
hashify::fnc_map!(value.as_bytes(),
b"asText" => { result.form = HeaderForm::Text;},
b"asAddresses" => { result.form = HeaderForm::Addresses;},
b"asGroupedAddresses" => { result.form = HeaderForm::GroupedAddresses;},
b"asMessageIds" => { result.form = HeaderForm::MessageIds;},
b"asDate" => { result.form = HeaderForm::Date;},
b"asURLs" => { result.form = HeaderForm::URLs;},
b"asRaw" => { result.form = HeaderForm::Raw; },
b"all" => { result.all = true; },
_ => {
return None;
}
);
}
2 if value == "all" && !result.all => {
result.all = true;
}
_ => return None,
}
}
if !result.header.is_empty() {
Some(result)
} else {
None
}
}
}
impl Display for HeaderProperty {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "header:{}", self.header)?;
self.form.fmt(f)?;
if self.all { write!(f, ":all") } else { Ok(()) }
}
}
impl Display for HeaderForm {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
HeaderForm::Raw => Ok(()),
HeaderForm::Text => write!(f, ":asText"),
HeaderForm::Addresses => write!(f, ":asAddresses"),
HeaderForm::GroupedAddresses => write!(f, ":asGroupedAddresses"),
HeaderForm::MessageIds => write!(f, ":asMessageIds"),
HeaderForm::Date => write!(f, ":asDate"),
HeaderForm::URLs => write!(f, ":asURLs"),
}
}
}
impl FromStr for EmailProperty {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
EmailProperty::parse(s, false).ok_or(())
}
}
#[derive(Debug, Clone, Default)]
pub struct EmailGetArguments {
pub body_properties: Option<Vec<MaybeInvalid<EmailProperty>>>,
pub fetch_text_body_values: Option<bool>,
pub fetch_html_body_values: Option<bool>,
pub fetch_all_body_values: Option<bool>,
pub max_body_value_bytes: Option<usize>,
}
#[derive(Debug, Clone, Default)]
pub struct EmailQueryArguments {
pub collapse_threads: Option<bool>,
}
#[derive(Debug, Clone, Default)]
pub struct EmailParseArguments {
pub body_properties: Option<Vec<MaybeInvalid<EmailProperty>>>,
pub fetch_text_body_values: Option<bool>,
pub fetch_html_body_values: Option<bool>,
pub fetch_all_body_values: Option<bool>,
pub max_body_value_bytes: Option<usize>,
}
impl<'de> DeserializeArguments<'de> for EmailGetArguments {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'de>,
{
hashify::fnc_map!(key.as_bytes(),
b"bodyProperties" => {
self.body_properties = map.next_value()?;
},
b"fetchTextBodyValues" => {
self.fetch_text_body_values = map.next_value()?;
},
b"fetchHTMLBodyValues" => {
self.fetch_html_body_values = map.next_value()?;
},
b"fetchAllBodyValues" => {
self.fetch_all_body_values = map.next_value()?;
},
b"maxBodyValueBytes" => {
self.max_body_value_bytes = map.next_value()?;
},
_ => {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
);
Ok(())
}
}
impl<'de> DeserializeArguments<'de> for EmailQueryArguments {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'de>,
{
if key == "collapseThreads" {
self.collapse_threads = map.next_value()?;
} else {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
Ok(())
}
}
impl<'de> DeserializeArguments<'de> for EmailParseArguments {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'de>,
{
hashify::fnc_map!(key.as_bytes(),
b"bodyProperties" => {
self.body_properties = map.next_value()?;
},
b"fetchTextBodyValues" => {
self.fetch_text_body_values = map.next_value()?;
},
b"fetchHTMLBodyValues" => {
self.fetch_html_body_values = map.next_value()?;
},
b"fetchAllBodyValues" => {
self.fetch_all_body_values = map.next_value()?;
},
b"maxBodyValueBytes" => {
self.max_body_value_bytes = map.next_value()?;
},
_ => {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
);
Ok(())
}
}
impl JmapObject for Email {
type Property = EmailProperty;
type Element = EmailValue;
type Id = Id;
type Filter = EmailFilter;
type Comparator = EmailComparator;
type GetArguments = EmailGetArguments;
type SetArguments<'de> = ();
type QueryArguments = EmailQueryArguments;
type CopyArguments = ();
type ParseArguments = EmailParseArguments;
const ID_PROPERTY: Self::Property = EmailProperty::Id;
}
#[derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, Debug, Clone, PartialEq, Eq)]
pub enum EmailFilter {
InMailbox(Id),
InMailboxOtherThan(Vec<Id>),
Before(UTCDate),
After(UTCDate),
MinSize(u32),
MaxSize(u32),
AllInThreadHaveKeyword(Keyword),
SomeInThreadHaveKeyword(Keyword),
NoneInThreadHaveKeyword(Keyword),
HasKeyword(Keyword),
NotKeyword(Keyword),
HasAttachment(bool),
From(String),
To(String),
Cc(String),
Bcc(String),
Subject(String),
Body(String),
Header(Vec<String>),
Text(String),
SentBefore(UTCDate),
SentAfter(UTCDate),
InThread(Id),
Id(Vec<Id>),
_T(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EmailComparator {
ReceivedAt,
Size,
From,
To,
Subject,
Cc,
SentAt,
ThreadId,
HasKeyword(Keyword),
AllInThreadHaveKeyword(Keyword),
SomeInThreadHaveKeyword(Keyword),
_T(String),
}
impl<'de> DeserializeArguments<'de> for EmailFilter {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'de>,
{
hashify::fnc_map!(key.as_bytes(),
b"inMailbox" => {
*self = EmailFilter::InMailbox(map.next_value()?);
},
b"inMailboxOtherThan" => {
*self = EmailFilter::InMailboxOtherThan(map.next_value()?);
},
b"before" => {
*self = EmailFilter::Before(map.next_value()?);
},
b"after" => {
*self = EmailFilter::After(map.next_value()?);
},
b"minSize" => {
*self = EmailFilter::MinSize(map.next_value()?);
},
b"maxSize" => {
*self = EmailFilter::MaxSize(map.next_value()?);
},
b"allInThreadHaveKeyword" => {
*self = EmailFilter::AllInThreadHaveKeyword(map.next_value()?);
},
b"someInThreadHaveKeyword" => {
*self = EmailFilter::SomeInThreadHaveKeyword(map.next_value()?);
},
b"noneInThreadHaveKeyword" => {
*self = EmailFilter::NoneInThreadHaveKeyword(map.next_value()?);
},
b"hasKeyword" => {
*self = EmailFilter::HasKeyword(map.next_value()?);
},
b"notKeyword" => {
*self = EmailFilter::NotKeyword(map.next_value()?);
},
b"hasAttachment" => {
*self = EmailFilter::HasAttachment(map.next_value()?);
},
b"from" => {
*self = EmailFilter::From(map.next_value()?);
},
b"to" => {
*self = EmailFilter::To(map.next_value()?);
},
b"cc" => {
*self = EmailFilter::Cc(map.next_value()?);
},
b"bcc" => {
*self = EmailFilter::Bcc(map.next_value()?);
},
b"subject" => {
*self = EmailFilter::Subject(map.next_value()?);
},
b"body" => {
*self = EmailFilter::Body(map.next_value()?);
},
b"header" => {
*self = EmailFilter::Header(map.next_value()?);
},
b"text" => {
*self = EmailFilter::Text(map.next_value()?);
},
b"sentBefore" => {
*self = EmailFilter::SentBefore(map.next_value()?);
},
b"sentAfter" => {
*self = EmailFilter::SentAfter(map.next_value()?);
},
b"inThread" => {
*self = EmailFilter::InThread(map.next_value()?);
},
b"id" => {
*self = EmailFilter::Id(map.next_value()?);
},
_ => {
*self = EmailFilter::_T(key.to_string());
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
);
Ok(())
}
}
impl<'de> DeserializeArguments<'de> for EmailComparator {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'de>,
{
if key == "property" {
let value = map.next_value::<Cow<str>>()?;
hashify::fnc_map!(value.as_bytes(),
b"receivedAt" => {
*self = EmailComparator::ReceivedAt;
},
b"size" => {
*self = EmailComparator::Size;
},
b"from" => {
*self = EmailComparator::From;
},
b"to" => {
*self = EmailComparator::To;
},
b"cc" => {
*self = EmailComparator::Cc;
},
b"subject" => {
*self = EmailComparator::Subject;
},
b"sentAt" => {
*self = EmailComparator::SentAt;
},
b"threadId" => {
*self = EmailComparator::ThreadId;
},
b"hasKeyword" => {
*self = EmailComparator::HasKeyword(self.take_keyword());
},
b"allInThreadHaveKeyword" => {
*self = EmailComparator::AllInThreadHaveKeyword(self.take_keyword());
},
b"someInThreadHaveKeyword" => {
*self = EmailComparator::SomeInThreadHaveKeyword(self.take_keyword());
},
_ => {
*self = EmailComparator::_T(key.to_string());
}
);
} else if key == "keyword" {
let keyword: Keyword = map.next_value()?;
match self {
EmailComparator::HasKeyword(_) => *self = EmailComparator::HasKeyword(keyword),
EmailComparator::AllInThreadHaveKeyword(_) => {
*self = EmailComparator::AllInThreadHaveKeyword(keyword)
}
EmailComparator::SomeInThreadHaveKeyword(_) => {
*self = EmailComparator::SomeInThreadHaveKeyword(keyword)
}
_ => {
*self = EmailComparator::HasKeyword(keyword);
}
}
} else {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
Ok(())
}
}
impl Default for EmailFilter {
fn default() -> Self {
EmailFilter::_T("".to_string())
}
}
impl Default for EmailComparator {
fn default() -> Self {
EmailComparator::_T("".to_string())
}
}
impl EmailComparator {
fn take_keyword(&mut self) -> Keyword {
match self {
EmailComparator::HasKeyword(k) => {
std::mem::replace(k, Keyword::Other(Default::default()))
}
EmailComparator::AllInThreadHaveKeyword(k) => {
std::mem::replace(k, Keyword::Other(Default::default()))
}
EmailComparator::SomeInThreadHaveKeyword(k) => {
std::mem::replace(k, Keyword::Other(Default::default()))
}
_ => Keyword::Other(Default::default()),
}
}
}
impl Display for EmailFilter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
EmailFilter::InMailbox(_) => "inMailbox",
EmailFilter::InMailboxOtherThan(_) => "inMailboxOtherThan",
EmailFilter::Before(_) => "before",
EmailFilter::After(_) => "after",
EmailFilter::MinSize(_) => "minSize",
EmailFilter::MaxSize(_) => "maxSize",
EmailFilter::AllInThreadHaveKeyword(_) => "allInThreadHaveKeyword",
EmailFilter::SomeInThreadHaveKeyword(_) => "someInThreadHaveKeyword",
EmailFilter::NoneInThreadHaveKeyword(_) => "noneInThreadHaveKeyword",
EmailFilter::HasKeyword(_) => "hasKeyword",
EmailFilter::NotKeyword(_) => "notKeyword",
EmailFilter::HasAttachment(_) => "hasAttachment",
EmailFilter::From(_) => "from",
EmailFilter::To(_) => "to",
EmailFilter::Cc(_) => "cc",
EmailFilter::Bcc(_) => "bcc",
EmailFilter::Subject(_) => "subject",
EmailFilter::Body(_) => "body",
EmailFilter::Header(_) => "header",
EmailFilter::Text(_) => "text",
EmailFilter::SentBefore(_) => "sentBefore",
EmailFilter::SentAfter(_) => "sentAfter",
EmailFilter::InThread(_) => "inThread",
EmailFilter::Id(_) => "id",
EmailFilter::_T(v) => v.as_str(),
})
}
}
impl Display for EmailComparator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
impl EmailComparator {
pub fn as_str(&self) -> &str {
match self {
EmailComparator::ReceivedAt => "receivedAt",
EmailComparator::Size => "size",
EmailComparator::From => "from",
EmailComparator::To => "to",
EmailComparator::Subject => "subject",
EmailComparator::Cc => "cc",
EmailComparator::SentAt => "sentAt",
EmailComparator::ThreadId => "threadId",
EmailComparator::HasKeyword(_) => "hasKeyword",
EmailComparator::AllInThreadHaveKeyword(_) => "allInThreadHaveKeyword",
EmailComparator::SomeInThreadHaveKeyword(_) => "someInThreadHaveKeyword",
EmailComparator::_T(v) => v.as_str(),
}
}
}
impl Serialize for EmailComparator {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl Filter<EmailFilter> {
pub fn is_immutable(&self) -> bool {
match self {
Filter::Property(f) => f.is_immutable(),
Filter::And | Filter::Or | Filter::Not | Filter::Close => true,
}
}
}
impl EmailFilter {
pub fn is_immutable(&self) -> bool {
matches!(
self,
EmailFilter::Before(_)
| EmailFilter::After(_)
| EmailFilter::MinSize(_)
| EmailFilter::MaxSize(_)
| EmailFilter::HasAttachment(_)
| EmailFilter::From(_)
| EmailFilter::To(_)
| EmailFilter::Cc(_)
| EmailFilter::Bcc(_)
| EmailFilter::Subject(_)
| EmailFilter::Body(_)
| EmailFilter::Header(_)
| EmailFilter::Text(_)
| EmailFilter::Id(_)
| EmailFilter::SentBefore(_)
| EmailFilter::SentAfter(_)
)
}
}
impl Comparator<EmailComparator> {
pub fn is_immutable(&self) -> bool {
self.property.is_immutable()
}
}
impl EmailComparator {
pub fn is_immutable(&self) -> bool {
matches!(
self,
EmailComparator::ReceivedAt
| EmailComparator::Size
| EmailComparator::From
| EmailComparator::To
| EmailComparator::Subject
| EmailComparator::Cc
| EmailComparator::SentAt
)
}
}
impl JmapObjectId for EmailValue {
fn as_id(&self) -> Option<Id> {
if let EmailValue::Id(id) = self {
Some(*id)
} else {
None
}
}
fn as_any_id(&self) -> Option<AnyId> {
match self {
EmailValue::Id(id) => Some(AnyId::Id(*id)),
EmailValue::BlobId(id) => Some(AnyId::BlobId(id.clone())),
_ => None,
}
}
fn as_id_ref(&self) -> Option<&str> {
if let EmailValue::IdReference(r) = self {
Some(r)
} else {
None
}
}
fn try_set_id(&mut self, new_id: AnyId) -> bool {
match new_id {
AnyId::Id(id) => {
*self = EmailValue::Id(id);
}
AnyId::BlobId(id) => {
*self = EmailValue::BlobId(id);
}
}
true
}
}
impl From<Id> for EmailValue {
fn from(id: Id) -> Self {
EmailValue::Id(id)
}
}
impl From<BlobId> for EmailValue {
fn from(id: BlobId) -> Self {
EmailValue::BlobId(id)
}
}
impl From<UTCDate> for EmailValue {
fn from(date: UTCDate) -> Self {
EmailValue::Date(date)
}
}
impl JmapObjectId for EmailProperty {
fn as_id(&self) -> Option<Id> {
if let EmailProperty::IdValue(id) = self {
Some(*id)
} else {
None
}
}
fn as_any_id(&self) -> Option<AnyId> {
if let EmailProperty::IdValue(id) = self {
Some(AnyId::Id(*id))
} else {
None
}
}
fn as_id_ref(&self) -> Option<&str> {
match self {
EmailProperty::IdReference(r) => Some(r),
EmailProperty::Pointer(value) => {
let value = value.as_slice();
match (value.first(), value.get(1)) {
(
Some(JsonPointerItem::Key(Key::Property(EmailProperty::MailboxIds))),
Some(JsonPointerItem::Key(Key::Property(EmailProperty::IdReference(r)))),
) => Some(r),
_ => None,
}
}
_ => None,
}
}
fn try_set_id(&mut self, new_id: AnyId) -> bool {
if let AnyId::Id(id) = new_id {
if let EmailProperty::Pointer(value) = self {
let value = value.as_mut_slice();
if let Some(value) = value.get_mut(1) {
*value = JsonPointerItem::Key(Key::Property(EmailProperty::IdValue(id)));
return true;
}
} else {
*self = EmailProperty::IdValue(id);
return true;
}
}
false
}
}
@@ -0,0 +1,483 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
object::{
AnyId, JmapObject, JmapObjectId, MaybeReference,
email::{EmailProperty, EmailValue},
parse_ref,
},
request::{MaybeInvalid, deserialize::DeserializeArguments, reference::MaybeIdReference},
types::date::UTCDate,
};
use jmap_tools::{Element, JsonPointer, JsonPointerItem, Key, Property, Value};
use std::{borrow::Cow, str::FromStr};
use types::{blob::BlobId, id::Id};
use utils::map::vec_map::VecMap;
#[derive(Debug, Clone, Default)]
pub struct EmailSubmission;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum EmailSubmissionProperty {
Id,
IdentityId,
ThreadId,
EmailId,
Envelope,
MailFrom,
RcptTo,
Email,
Parameters,
SendAt,
UndoStatus,
DeliveryStatus,
SmtpReply,
Delivered,
Displayed,
DsnBlobIds,
MdnBlobIds,
Pointer(JsonPointer<EmailSubmissionProperty>),
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum EmailSubmissionValue {
Id(Id),
Date(UTCDate),
BlobId(BlobId),
UndoStatus(UndoStatus),
Delivered(Delivered),
Displayed(Displayed),
IdReference(String),
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum UndoStatus {
Pending,
Final,
Canceled,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Delivered {
Queued,
Yes,
No,
Unknown,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Displayed {
Yes,
Unknown,
}
impl Property for EmailSubmissionProperty {
fn try_parse(key: Option<&Key<'_, Self>>, value: &str) -> Option<Self> {
EmailSubmissionProperty::parse(value, key.is_none())
}
fn to_cow(&self) -> Cow<'static, str> {
match self {
EmailSubmissionProperty::DeliveryStatus => "deliveryStatus",
EmailSubmissionProperty::DsnBlobIds => "dsnBlobIds",
EmailSubmissionProperty::Email => "email",
EmailSubmissionProperty::Envelope => "envelope",
EmailSubmissionProperty::Id => "id",
EmailSubmissionProperty::IdentityId => "identityId",
EmailSubmissionProperty::MdnBlobIds => "mdnBlobIds",
EmailSubmissionProperty::SendAt => "sendAt",
EmailSubmissionProperty::ThreadId => "threadId",
EmailSubmissionProperty::UndoStatus => "undoStatus",
EmailSubmissionProperty::Parameters => "parameters",
EmailSubmissionProperty::SmtpReply => "smtpReply",
EmailSubmissionProperty::Delivered => "delivered",
EmailSubmissionProperty::Displayed => "displayed",
EmailSubmissionProperty::MailFrom => "mailFrom",
EmailSubmissionProperty::RcptTo => "rcptTo",
EmailSubmissionProperty::EmailId => "emailId",
EmailSubmissionProperty::Pointer(json_pointer) => {
return json_pointer.to_string().into();
}
}
.into()
}
}
impl Element for EmailSubmissionValue {
type Property = EmailSubmissionProperty;
fn try_parse<P>(key: &Key<'_, Self::Property>, value: &str) -> Option<Self> {
if let Key::Property(prop) = key {
match prop.patch_or_prop() {
EmailSubmissionProperty::Id
| EmailSubmissionProperty::ThreadId
| EmailSubmissionProperty::IdentityId
| EmailSubmissionProperty::EmailId => match parse_ref(value) {
MaybeReference::Value(v) => Some(EmailSubmissionValue::Id(v)),
MaybeReference::Reference(v) => Some(EmailSubmissionValue::IdReference(v)),
MaybeReference::ParseError => None,
},
EmailSubmissionProperty::MdnBlobIds | EmailSubmissionProperty::DsnBlobIds => {
match parse_ref(value) {
MaybeReference::Value(v) => Some(EmailSubmissionValue::BlobId(v)),
MaybeReference::Reference(v) => Some(EmailSubmissionValue::IdReference(v)),
MaybeReference::ParseError => None,
}
}
EmailSubmissionProperty::SendAt => UTCDate::from_str(value)
.ok()
.map(EmailSubmissionValue::Date),
EmailSubmissionProperty::UndoStatus => {
UndoStatus::parse(value).map(EmailSubmissionValue::UndoStatus)
}
EmailSubmissionProperty::Delivered => {
Delivered::parse(value).map(EmailSubmissionValue::Delivered)
}
EmailSubmissionProperty::Displayed => {
Displayed::parse(value).map(EmailSubmissionValue::Displayed)
}
_ => None,
}
} else {
None
}
}
fn to_cow(&self) -> Cow<'static, str> {
match self {
EmailSubmissionValue::Id(id) => id.to_string().into(),
EmailSubmissionValue::Date(utcdate) => utcdate.to_string().into(),
EmailSubmissionValue::BlobId(blob_id) => blob_id.to_string().into(),
EmailSubmissionValue::IdReference(r) => format!("#{r}").into(),
EmailSubmissionValue::UndoStatus(undo_status) => undo_status.as_str().into(),
EmailSubmissionValue::Delivered(delivered) => delivered.as_str().into(),
EmailSubmissionValue::Displayed(displayed) => displayed.as_str().into(),
}
}
}
impl EmailSubmissionProperty {
fn parse(value: &str, allow_patch: bool) -> Option<Self> {
hashify::tiny_map!(value.as_bytes(),
"id" => EmailSubmissionProperty::Id,
"identityId" => EmailSubmissionProperty::IdentityId,
"threadId" => EmailSubmissionProperty::ThreadId,
"emailId" => EmailSubmissionProperty::EmailId,
"envelope" => EmailSubmissionProperty::Envelope,
"mailFrom" => EmailSubmissionProperty::MailFrom,
"rcptTo" => EmailSubmissionProperty::RcptTo,
"email" => EmailSubmissionProperty::Email,
"parameters" => EmailSubmissionProperty::Parameters,
"sendAt" => EmailSubmissionProperty::SendAt,
"undoStatus" => EmailSubmissionProperty::UndoStatus,
"deliveryStatus" => EmailSubmissionProperty::DeliveryStatus,
"smtpReply" => EmailSubmissionProperty::SmtpReply,
"delivered" => EmailSubmissionProperty::Delivered,
"displayed" => EmailSubmissionProperty::Displayed,
"dsnBlobIds" => EmailSubmissionProperty::DsnBlobIds,
"mdnBlobIds" => EmailSubmissionProperty::MdnBlobIds,
)
.or_else(|| {
if allow_patch && value.contains('/') {
EmailSubmissionProperty::Pointer(JsonPointer::parse(value)).into()
} else {
None
}
})
}
fn patch_or_prop(&self) -> &EmailSubmissionProperty {
if let EmailSubmissionProperty::Pointer(ptr) = self
&& let Some(JsonPointerItem::Key(Key::Property(prop))) = ptr.last()
{
prop
} else {
self
}
}
}
impl UndoStatus {
fn parse(value: &str) -> Option<Self> {
hashify::tiny_map!(value.as_bytes(),
b"pending" => UndoStatus::Pending,
b"final" => UndoStatus::Final,
b"canceled" => UndoStatus::Canceled,
)
}
fn as_str(&self) -> &'static str {
match self {
UndoStatus::Pending => "pending",
UndoStatus::Final => "final",
UndoStatus::Canceled => "canceled",
}
}
}
impl Delivered {
fn parse(value: &str) -> Option<Self> {
hashify::tiny_map!(value.as_bytes(),
b"queued" => Delivered::Queued,
b"yes" => Delivered::Yes,
b"no" => Delivered::No,
b"unknown" => Delivered::Unknown,
)
}
fn as_str(&self) -> &'static str {
match self {
Delivered::Queued => "queued",
Delivered::Yes => "yes",
Delivered::No => "no",
Delivered::Unknown => "unknown",
}
}
}
impl Displayed {
fn parse(value: &str) -> Option<Self> {
hashify::tiny_map!(value.as_bytes(),
b"yes" => Displayed::Yes,
b"unknown" => Displayed::Unknown,
)
}
fn as_str(&self) -> &'static str {
match self {
Displayed::Yes => "yes",
Displayed::Unknown => "unknown",
}
}
}
#[derive(Debug, Clone, Default)]
pub struct EmailSubmissionSetArguments<'x> {
pub on_success_update_email:
Option<VecMap<MaybeIdReference<Id>, Value<'x, EmailProperty, EmailValue>>>,
pub on_success_destroy_email: Option<Vec<MaybeIdReference<Id>>>,
}
impl<'x> DeserializeArguments<'x> for EmailSubmissionSetArguments<'x> {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'x>,
{
hashify::fnc_map!(key.as_bytes(),
b"onSuccessUpdateEmail" => {
self.on_success_update_email = map.next_value()?;
},
b"onSuccessDestroyEmail" => {
self.on_success_destroy_email = map.next_value()?;
},
_ => {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
);
Ok(())
}
}
impl FromStr for EmailSubmissionProperty {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
EmailSubmissionProperty::parse(s, false).ok_or(())
}
}
impl<'de> serde::Deserialize<'de> for UndoStatus {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
UndoStatus::parse(<&str>::deserialize(deserializer)?)
.ok_or_else(|| serde::de::Error::custom("invalid JMAP UndoStatus"))
}
}
impl JmapObject for EmailSubmission {
type Property = EmailSubmissionProperty;
type Element = EmailSubmissionValue;
type Id = Id;
type Filter = EmailSubmissionFilter;
type Comparator = EmailSubmissionComparator;
type GetArguments = ();
type SetArguments<'de> = EmailSubmissionSetArguments<'de>;
type QueryArguments = ();
type CopyArguments = ();
type ParseArguments = ();
const ID_PROPERTY: Self::Property = EmailSubmissionProperty::Id;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EmailSubmissionFilter {
IdentityIds(Vec<MaybeInvalid<Id>>),
EmailIds(Vec<MaybeInvalid<Id>>),
ThreadIds(Vec<MaybeInvalid<Id>>),
Before(UTCDate),
After(UTCDate),
UndoStatus(UndoStatus),
_T(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EmailSubmissionComparator {
EmailId,
ThreadId,
SentAt,
_T(String),
}
impl<'de> DeserializeArguments<'de> for EmailSubmissionFilter {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'de>,
{
hashify::fnc_map!(key.as_bytes(),
b"identityIds" => {
*self = EmailSubmissionFilter::IdentityIds(map.next_value()?);
},
b"emailIds" => {
*self = EmailSubmissionFilter::EmailIds(map.next_value()?);
},
b"threadIds" => {
*self = EmailSubmissionFilter::ThreadIds(map.next_value()?);
},
b"before" => {
*self = EmailSubmissionFilter::Before(map.next_value()?);
},
b"after" => {
*self = EmailSubmissionFilter::After(map.next_value()?);
},
b"undoStatus" => {
*self = EmailSubmissionFilter::UndoStatus(map.next_value()?);
},
_ => {
*self = EmailSubmissionFilter::_T(key.to_string());
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
);
Ok(())
}
}
impl<'de> DeserializeArguments<'de> for EmailSubmissionComparator {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'de>,
{
if key == "property" {
let value = map.next_value::<Cow<str>>()?;
hashify::fnc_map!(value.as_bytes(),
b"emailId" => {
*self = EmailSubmissionComparator::EmailId;
},
b"threadId" => {
*self = EmailSubmissionComparator::ThreadId;
},
b"sentAt" => {
*self = EmailSubmissionComparator::SentAt;
},
_ => {
*self = EmailSubmissionComparator::_T(key.to_string());
}
);
} else {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
Ok(())
}
}
impl Default for EmailSubmissionFilter {
fn default() -> Self {
EmailSubmissionFilter::_T("".to_string())
}
}
impl Default for EmailSubmissionComparator {
fn default() -> Self {
EmailSubmissionComparator::_T("".to_string())
}
}
impl From<Id> for EmailSubmissionValue {
fn from(id: Id) -> Self {
EmailSubmissionValue::Id(id)
}
}
impl JmapObjectId for EmailSubmissionValue {
fn as_id(&self) -> Option<Id> {
match self {
EmailSubmissionValue::Id(id) => Some(*id),
_ => None,
}
}
fn as_any_id(&self) -> Option<AnyId> {
match self {
EmailSubmissionValue::Id(id) => Some(AnyId::Id(*id)),
EmailSubmissionValue::BlobId(blob_id) => Some(AnyId::BlobId(blob_id.clone())),
_ => None,
}
}
fn as_id_ref(&self) -> Option<&str> {
if let EmailSubmissionValue::IdReference(r) = self {
Some(r)
} else {
None
}
}
fn try_set_id(&mut self, new_id: AnyId) -> bool {
match new_id {
AnyId::Id(id) => {
*self = EmailSubmissionValue::Id(id);
}
AnyId::BlobId(blob_id) => {
*self = EmailSubmissionValue::BlobId(blob_id);
}
}
true
}
}
impl JmapObjectId for EmailSubmissionProperty {
fn as_id(&self) -> Option<Id> {
None
}
fn as_any_id(&self) -> Option<AnyId> {
None
}
fn as_id_ref(&self) -> Option<&str> {
None
}
fn try_set_id(&mut self, _: AnyId) -> bool {
false
}
}
+826
View File
@@ -0,0 +1,826 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
object::{
AnyId, JmapObject, JmapObjectId, JmapRight, JmapSharedObject, MaybeReference, parse_ref,
},
request::{MaybeInvalid, deserialize::DeserializeArguments},
types::date::UTCDate,
};
use jmap_tools::{Element, JsonPointer, JsonPointerItem, Key, Property};
use std::{borrow::Cow, fmt::Display, str::FromStr};
use types::{acl::Acl, blob::BlobId, id::Id};
use utils::glob::GlobPattern;
#[derive(Debug, Clone, Default)]
pub struct FileNode;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum FileNodeProperty {
Id,
ParentId,
BlobId,
Size,
Name,
Type,
NodeType,
Target,
Created,
Modified,
Accessed,
Changed,
Executable,
Role,
MyRights,
ShareWith,
IsSubscribed,
IdValue(Id),
Rights(FileNodeRight),
Pointer(JsonPointer<FileNodeProperty>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum FileNodeRight {
MayRead,
MayAddChildren,
MayRename,
MayDelete,
MayModifyContent,
MayShare,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum FileNodeNodeType {
File,
Directory,
Symlink,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum FileNodeRole {
Root,
Home,
Temp,
Trash,
Documents,
Downloads,
Music,
Pictures,
Videos,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum FileNodeValue {
Id(Id),
Date(UTCDate),
BlobId(BlobId),
IdReference(String),
}
impl Property for FileNodeProperty {
fn try_parse(key: Option<&Key<'_, Self>>, value: &str) -> Option<Self> {
let allow_patch = key.is_none();
if let Some(Key::Property(key)) = key {
match key.patch_or_prop() {
FileNodeProperty::ShareWith => {
Id::from_str(value).ok().map(FileNodeProperty::IdValue)
}
_ => FileNodeProperty::parse(value, allow_patch),
}
} else {
FileNodeProperty::parse(value, allow_patch)
}
}
fn to_cow(&self) -> Cow<'static, str> {
match self {
FileNodeProperty::Id => "id",
FileNodeProperty::ParentId => "parentId",
FileNodeProperty::BlobId => "blobId",
FileNodeProperty::Size => "size",
FileNodeProperty::Name => "name",
FileNodeProperty::Type => "type",
FileNodeProperty::NodeType => "nodeType",
FileNodeProperty::Target => "target",
FileNodeProperty::Created => "created",
FileNodeProperty::Modified => "modified",
FileNodeProperty::Accessed => "accessed",
FileNodeProperty::Changed => "changed",
FileNodeProperty::Executable => "executable",
FileNodeProperty::Role => "role",
FileNodeProperty::MyRights => "myRights",
FileNodeProperty::ShareWith => "shareWith",
FileNodeProperty::IsSubscribed => "isSubscribed",
FileNodeProperty::Rights(file_right) => file_right.as_str(),
FileNodeProperty::Pointer(json_pointer) => return json_pointer.to_string().into(),
FileNodeProperty::IdValue(id) => return id.to_string().into(),
}
.into()
}
}
impl FileNodeRight {
pub fn as_str(&self) -> &'static str {
match self {
FileNodeRight::MayRead => "mayRead",
FileNodeRight::MayAddChildren => "mayAddChildren",
FileNodeRight::MayRename => "mayRename",
FileNodeRight::MayDelete => "mayDelete",
FileNodeRight::MayModifyContent => "mayModifyContent",
FileNodeRight::MayShare => "mayShare",
}
}
}
impl FileNodeNodeType {
pub fn as_str(&self) -> &'static str {
match self {
FileNodeNodeType::File => "file",
FileNodeNodeType::Directory => "directory",
FileNodeNodeType::Symlink => "symlink",
}
}
pub fn parse(value: &str) -> Option<Self> {
hashify::tiny_map!(value.as_bytes(),
b"file" => FileNodeNodeType::File,
b"directory" => FileNodeNodeType::Directory,
b"symlink" => FileNodeNodeType::Symlink,
)
}
}
impl FromStr for FileNodeNodeType {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
FileNodeNodeType::parse(s).ok_or(())
}
}
impl Display for FileNodeNodeType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
impl FileNodeRole {
pub fn as_str(&self) -> &'static str {
match self {
FileNodeRole::Root => "root",
FileNodeRole::Home => "home",
FileNodeRole::Temp => "temp",
FileNodeRole::Trash => "trash",
FileNodeRole::Documents => "documents",
FileNodeRole::Downloads => "downloads",
FileNodeRole::Music => "music",
FileNodeRole::Pictures => "pictures",
FileNodeRole::Videos => "videos",
}
}
pub fn parse(value: &str) -> Option<Self> {
hashify::tiny_map!(value.as_bytes(),
b"root" => FileNodeRole::Root,
b"home" => FileNodeRole::Home,
b"temp" => FileNodeRole::Temp,
b"trash" => FileNodeRole::Trash,
b"documents" => FileNodeRole::Documents,
b"downloads" => FileNodeRole::Downloads,
b"music" => FileNodeRole::Music,
b"pictures" => FileNodeRole::Pictures,
b"videos" => FileNodeRole::Videos,
)
}
}
impl FromStr for FileNodeRole {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
FileNodeRole::parse(s).ok_or(())
}
}
impl Display for FileNodeRole {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
impl Element for FileNodeValue {
type Property = FileNodeProperty;
fn try_parse<P>(key: &Key<'_, Self::Property>, value: &str) -> Option<Self> {
if let Key::Property(prop) = key {
match prop.patch_or_prop() {
FileNodeProperty::Id | FileNodeProperty::ParentId => match parse_ref(value) {
MaybeReference::Value(v) => Some(FileNodeValue::Id(v)),
MaybeReference::Reference(v) => Some(FileNodeValue::IdReference(v)),
MaybeReference::ParseError => None,
},
FileNodeProperty::BlobId => match parse_ref(value) {
MaybeReference::Value(v) => Some(FileNodeValue::BlobId(v)),
MaybeReference::Reference(v) => Some(FileNodeValue::IdReference(v)),
MaybeReference::ParseError => None,
},
FileNodeProperty::Created
| FileNodeProperty::Modified
| FileNodeProperty::Accessed
| FileNodeProperty::Changed => {
UTCDate::from_str(value).ok().map(FileNodeValue::Date)
}
_ => None,
}
} else {
None
}
}
fn to_cow(&self) -> Cow<'static, str> {
match self {
FileNodeValue::Id(id) => id.to_string().into(),
FileNodeValue::Date(utcdate) => utcdate.to_string().into(),
FileNodeValue::BlobId(blob_id) => blob_id.to_string().into(),
FileNodeValue::IdReference(r) => format!("#{r}").into(),
}
}
}
impl FileNodeProperty {
fn parse(value: &str, allow_patch: bool) -> Option<Self> {
hashify::tiny_map!(value.as_bytes(),
b"id" => FileNodeProperty::Id,
b"parentId" => FileNodeProperty::ParentId,
b"blobId" => FileNodeProperty::BlobId,
b"size" => FileNodeProperty::Size,
b"name" => FileNodeProperty::Name,
b"type" => FileNodeProperty::Type,
b"nodeType" => FileNodeProperty::NodeType,
b"target" => FileNodeProperty::Target,
b"created" => FileNodeProperty::Created,
b"modified" => FileNodeProperty::Modified,
b"accessed" => FileNodeProperty::Accessed,
b"changed" => FileNodeProperty::Changed,
b"executable" => FileNodeProperty::Executable,
b"role" => FileNodeProperty::Role,
b"myRights" => FileNodeProperty::MyRights,
b"shareWith" => FileNodeProperty::ShareWith,
b"isSubscribed" => FileNodeProperty::IsSubscribed,
b"mayRead" => FileNodeProperty::Rights(FileNodeRight::MayRead),
b"mayAddChildren" => FileNodeProperty::Rights(FileNodeRight::MayAddChildren),
b"mayRename" => FileNodeProperty::Rights(FileNodeRight::MayRename),
b"mayDelete" => FileNodeProperty::Rights(FileNodeRight::MayDelete),
b"mayModifyContent" => FileNodeProperty::Rights(FileNodeRight::MayModifyContent),
b"mayShare" => FileNodeProperty::Rights(FileNodeRight::MayShare),
)
.or_else(|| {
if allow_patch && value.contains('/') {
FileNodeProperty::Pointer(JsonPointer::parse(value)).into()
} else {
None
}
})
}
fn patch_or_prop(&self) -> &FileNodeProperty {
if let FileNodeProperty::Pointer(ptr) = self
&& let Some(JsonPointerItem::Key(Key::Property(prop))) = ptr.last()
{
prop
} else {
self
}
}
}
#[derive(Debug, Clone, Default)]
pub struct FileNodeSetArguments {
pub on_destroy_remove_children: Option<bool>,
pub on_exists: OnExists,
pub compare_case_insensitively: Option<bool>,
}
pub type FileNodeCopyArguments = FileNodeSetArguments;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum OnExists {
#[default]
Reject,
Replace,
Rename,
Newest,
}
impl<'de> serde::Deserialize<'de> for OnExists {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value: Option<Cow<'_, str>> = Option::deserialize(deserializer)?;
match value.as_deref() {
Some("replace") => Ok(OnExists::Replace),
Some("rename") => Ok(OnExists::Rename),
Some("newest") => Ok(OnExists::Newest),
None | Some("") => Ok(OnExists::Reject),
Some(other) => Err(serde::de::Error::custom(format!(
"Invalid onExists value: {other:?}"
))),
}
}
}
impl<'x> DeserializeArguments<'x> for FileNodeSetArguments {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'x>,
{
hashify::fnc_map!(key.as_bytes(),
b"onDestroyRemoveChildren" => {
self.on_destroy_remove_children = map.next_value()?;
},
b"onExists" => {
self.on_exists = map.next_value()?;
},
b"compareCaseInsensitively" => {
self.compare_case_insensitively = map.next_value()?;
},
_ => {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
);
Ok(())
}
}
#[derive(Debug, Clone, Default)]
pub struct FileNodeGetArguments {
pub fetch_parents: Option<bool>,
}
impl<'x> DeserializeArguments<'x> for FileNodeGetArguments {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'x>,
{
if key == "fetchParents" {
self.fetch_parents = map.next_value()?;
} else {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
Ok(())
}
}
#[derive(Debug, Clone, Default)]
pub struct FileNodeQueryArguments {
pub depth: Option<u32>,
}
impl<'x> DeserializeArguments<'x> for FileNodeQueryArguments {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'x>,
{
if key == "depth" {
self.depth = map.next_value()?;
} else {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
Ok(())
}
}
impl FromStr for FileNodeProperty {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
FileNodeProperty::parse(s, false).ok_or(())
}
}
impl JmapObject for FileNode {
type Property = FileNodeProperty;
type Element = FileNodeValue;
type Id = Id;
type Filter = FileNodeFilter;
type Comparator = FileNodeComparator;
type GetArguments = FileNodeGetArguments;
type SetArguments<'de> = FileNodeSetArguments;
type QueryArguments = FileNodeQueryArguments;
type CopyArguments = FileNodeCopyArguments;
type ParseArguments = ();
const ID_PROPERTY: Self::Property = FileNodeProperty::Id;
}
impl JmapSharedObject for FileNode {
type Right = FileNodeRight;
const SHARE_WITH_PROPERTY: Self::Property = FileNodeProperty::ShareWith;
}
impl From<Id> for FileNodeProperty {
fn from(id: Id) -> Self {
FileNodeProperty::IdValue(id)
}
}
impl JmapRight for FileNodeRight {
fn to_acl(&self) -> &'static [Acl] {
match self {
FileNodeRight::MayRead => &[Acl::Read, Acl::ReadItems],
FileNodeRight::MayAddChildren => &[Acl::AddItems],
FileNodeRight::MayRename => &[Acl::Modify],
FileNodeRight::MayDelete => &[Acl::Delete, Acl::RemoveItems],
FileNodeRight::MayModifyContent => &[Acl::ModifyItems],
FileNodeRight::MayShare => &[Acl::Share],
}
}
fn all_rights() -> &'static [Self] {
&[
FileNodeRight::MayRead,
FileNodeRight::MayAddChildren,
FileNodeRight::MayRename,
FileNodeRight::MayDelete,
FileNodeRight::MayModifyContent,
FileNodeRight::MayShare,
]
}
}
impl From<FileNodeRight> for FileNodeProperty {
fn from(right: FileNodeRight) -> Self {
FileNodeProperty::Rights(right)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FileNodeFilter {
IsTopLevel(bool),
ParentId(MaybeInvalid<Id>),
AncestorId(MaybeInvalid<Id>),
DescendantId(MaybeInvalid<Id>),
NodeType(String),
Role(String),
HasAnyRole(bool),
BlobId(MaybeInvalid<BlobId>),
IsExecutable(bool),
CreatedBefore(UTCDate),
CreatedAfter(UTCDate),
ModifiedBefore(UTCDate),
ModifiedAfter(UTCDate),
AccessedBefore(UTCDate),
AccessedAfter(UTCDate),
MinSize(u64),
MaxSize(u64),
Name(String),
NameMatch(GlobPattern),
Type(String),
TypeMatch(GlobPattern),
Text(String),
Body(String),
_T(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FileNodeComparator {
Name,
Size,
Created,
Modified,
Type,
NodeType,
Tree,
_T(String),
}
impl<'de> DeserializeArguments<'de> for FileNodeFilter {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'de>,
{
hashify::fnc_map!(key.as_bytes(),
b"isTopLevel" => {
*self = FileNodeFilter::IsTopLevel(map.next_value()?);
},
b"parentId" => {
*self = FileNodeFilter::ParentId(map.next_value()?);
},
b"ancestorId" => {
*self = FileNodeFilter::AncestorId(map.next_value()?);
},
b"descendantId" => {
*self = FileNodeFilter::DescendantId(map.next_value()?);
},
b"nodeType" => {
*self = FileNodeFilter::NodeType(map.next_value()?);
},
b"role" => {
*self = FileNodeFilter::Role(map.next_value()?);
},
b"hasAnyRole" => {
*self = FileNodeFilter::HasAnyRole(map.next_value()?);
},
b"blobId" => {
*self = FileNodeFilter::BlobId(map.next_value()?);
},
b"isExecutable" => {
*self = FileNodeFilter::IsExecutable(map.next_value()?);
},
b"createdBefore" => {
*self = FileNodeFilter::CreatedBefore(map.next_value()?);
},
b"createdAfter" => {
*self = FileNodeFilter::CreatedAfter(map.next_value()?);
},
b"modifiedBefore" => {
*self = FileNodeFilter::ModifiedBefore(map.next_value()?);
},
b"modifiedAfter" => {
*self = FileNodeFilter::ModifiedAfter(map.next_value()?);
},
b"accessedBefore" => {
*self = FileNodeFilter::AccessedBefore(map.next_value()?);
},
b"accessedAfter" => {
*self = FileNodeFilter::AccessedAfter(map.next_value()?);
},
b"minSize" => {
*self = FileNodeFilter::MinSize(map.next_value()?);
},
b"maxSize" => {
*self = FileNodeFilter::MaxSize(map.next_value()?);
},
b"name" => {
*self = FileNodeFilter::Name(map.next_value()?);
},
b"nameMatch" => {
*self = FileNodeFilter::NameMatch(map.next_value()?);
},
b"type" => {
*self = FileNodeFilter::Type(map.next_value()?);
},
b"typeMatch" => {
*self = FileNodeFilter::TypeMatch(map.next_value()?);
},
b"body" => {
*self = FileNodeFilter::Body(map.next_value()?);
},
b"text" => {
*self = FileNodeFilter::Text(map.next_value()?);
},
_ => {
*self = FileNodeFilter::_T(key.to_string());
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
);
Ok(())
}
}
impl<'de> DeserializeArguments<'de> for FileNodeComparator {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'de>,
{
if key == "property" {
let value = map.next_value::<Cow<str>>()?;
hashify::fnc_map!(value.as_bytes(),
b"name" => {
*self = FileNodeComparator::Name;
},
b"size" => {
*self = FileNodeComparator::Size;
},
b"created" => {
*self = FileNodeComparator::Created;
},
b"modified" => {
*self = FileNodeComparator::Modified;
},
b"type" => {
*self = FileNodeComparator::Type;
},
b"nodeType" => {
*self = FileNodeComparator::NodeType;
},
b"tree" => {
*self = FileNodeComparator::Tree;
},
_ => {
*self = FileNodeComparator::_T(value.into_owned());
}
);
} else {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
Ok(())
}
}
impl Default for FileNodeFilter {
fn default() -> Self {
FileNodeFilter::_T("".to_string())
}
}
impl Default for FileNodeComparator {
fn default() -> Self {
FileNodeComparator::_T("".to_string())
}
}
impl From<Id> for FileNodeValue {
fn from(id: Id) -> Self {
FileNodeValue::Id(id)
}
}
impl JmapObjectId for FileNodeValue {
fn as_id(&self) -> Option<Id> {
match self {
FileNodeValue::Id(id) => Some(*id),
_ => None,
}
}
fn as_any_id(&self) -> Option<AnyId> {
match self {
FileNodeValue::Id(id) => Some(AnyId::Id(*id)),
FileNodeValue::BlobId(blob_id) => Some(AnyId::BlobId(blob_id.clone())),
_ => None,
}
}
fn as_id_ref(&self) -> Option<&str> {
if let FileNodeValue::IdReference(r) = self {
Some(r)
} else {
None
}
}
fn try_set_id(&mut self, new_id: AnyId) -> bool {
match new_id {
AnyId::Id(id) => {
*self = FileNodeValue::Id(id);
}
AnyId::BlobId(blob_id) => {
*self = FileNodeValue::BlobId(blob_id);
}
}
true
}
}
impl FileNodeFilter {
pub fn into_string(self) -> Cow<'static, str> {
match self {
FileNodeFilter::IsTopLevel(_) => "isTopLevel",
FileNodeFilter::ParentId(_) => "parentId",
FileNodeFilter::AncestorId(_) => "ancestorId",
FileNodeFilter::DescendantId(_) => "descendantId",
FileNodeFilter::NodeType(_) => "nodeType",
FileNodeFilter::Role(_) => "role",
FileNodeFilter::HasAnyRole(_) => "hasAnyRole",
FileNodeFilter::BlobId(_) => "blobId",
FileNodeFilter::IsExecutable(_) => "isExecutable",
FileNodeFilter::CreatedBefore(_) => "createdBefore",
FileNodeFilter::CreatedAfter(_) => "createdAfter",
FileNodeFilter::ModifiedBefore(_) => "modifiedBefore",
FileNodeFilter::ModifiedAfter(_) => "modifiedAfter",
FileNodeFilter::AccessedBefore(_) => "accessedBefore",
FileNodeFilter::AccessedAfter(_) => "accessedAfter",
FileNodeFilter::MinSize(_) => "minSize",
FileNodeFilter::MaxSize(_) => "maxSize",
FileNodeFilter::Name(_) => "name",
FileNodeFilter::NameMatch(_) => "nameMatch",
FileNodeFilter::Type(_) => "type",
FileNodeFilter::TypeMatch(_) => "typeMatch",
FileNodeFilter::Text(_) => "text",
FileNodeFilter::Body(_) => "body",
FileNodeFilter::_T(s) => return s.into(),
}
.into()
}
}
impl FileNodeComparator {
pub fn as_str(&self) -> &str {
match self {
FileNodeComparator::Name => "name",
FileNodeComparator::Size => "size",
FileNodeComparator::Created => "created",
FileNodeComparator::Modified => "modified",
FileNodeComparator::Type => "type",
FileNodeComparator::NodeType => "nodeType",
FileNodeComparator::Tree => "tree",
FileNodeComparator::_T(s) => s.as_ref(),
}
}
pub fn into_string(self) -> Cow<'static, str> {
match self {
FileNodeComparator::Name => "name",
FileNodeComparator::Size => "size",
FileNodeComparator::Created => "created",
FileNodeComparator::Modified => "modified",
FileNodeComparator::Type => "type",
FileNodeComparator::NodeType => "nodeType",
FileNodeComparator::Tree => "tree",
FileNodeComparator::_T(s) => return s.into(),
}
.into()
}
}
impl serde::Serialize for FileNodeComparator {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl TryFrom<FileNodeProperty> for Id {
type Error = ();
fn try_from(value: FileNodeProperty) -> Result<Self, Self::Error> {
if let FileNodeProperty::IdValue(id) = value {
Ok(id)
} else {
Err(())
}
}
}
impl TryFrom<FileNodeProperty> for FileNodeRight {
type Error = ();
fn try_from(value: FileNodeProperty) -> Result<Self, Self::Error> {
if let FileNodeProperty::Rights(right) = value {
Ok(right)
} else {
Err(())
}
}
}
impl JmapObjectId for FileNodeProperty {
fn as_id(&self) -> Option<Id> {
if let FileNodeProperty::IdValue(id) = self {
Some(*id)
} else {
None
}
}
fn as_any_id(&self) -> Option<AnyId> {
if let FileNodeProperty::IdValue(id) = self {
Some(AnyId::Id(*id))
} else {
None
}
}
fn as_id_ref(&self) -> Option<&str> {
None
}
fn try_set_id(&mut self, new_id: AnyId) -> bool {
if let AnyId::Id(id) = new_id {
*self = FileNodeProperty::IdValue(id);
true
} else {
false
}
}
}
impl Display for FileNodeProperty {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_cow())
}
}
+190
View File
@@ -0,0 +1,190 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::object::{AnyId, JmapObject, JmapObjectId};
use jmap_tools::{Element, JsonPointer, JsonPointerItem, Key, Property};
use std::{borrow::Cow, str::FromStr};
use types::id::Id;
#[derive(Debug, Clone, Default)]
pub struct Identity;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum IdentityProperty {
Id,
Name,
Email,
ReplyTo,
Bcc,
TextSignature,
HtmlSignature,
MayDelete,
// Other
Pointer(JsonPointer<IdentityProperty>),
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum IdentityValue {
Id(Id),
}
impl Property for IdentityProperty {
fn try_parse(key: Option<&Key<'_, Self>>, value: &str) -> Option<Self> {
IdentityProperty::parse(value, key.is_none())
}
fn to_cow(&self) -> Cow<'static, str> {
match self {
IdentityProperty::Bcc => "bcc",
IdentityProperty::Email => "email",
IdentityProperty::HtmlSignature => "htmlSignature",
IdentityProperty::Id => "id",
IdentityProperty::MayDelete => "mayDelete",
IdentityProperty::Name => "name",
IdentityProperty::ReplyTo => "replyTo",
IdentityProperty::TextSignature => "textSignature",
IdentityProperty::Pointer(json_pointer) => return json_pointer.to_string().into(),
}
.into()
}
}
impl Element for IdentityValue {
type Property = IdentityProperty;
fn try_parse<P>(key: &Key<'_, Self::Property>, value: &str) -> Option<Self> {
if let Key::Property(prop) = key {
match prop.patch_or_prop() {
IdentityProperty::Id => Id::from_str(value).ok().map(IdentityValue::Id),
_ => None,
}
} else {
None
}
}
fn to_cow(&self) -> Cow<'static, str> {
match self {
IdentityValue::Id(id) => id.to_string().into(),
}
}
}
impl IdentityProperty {
fn parse(value: &str, allow_patch: bool) -> Option<Self> {
hashify::tiny_map!(value.as_bytes(),
b"id" => IdentityProperty::Id,
b"name" => IdentityProperty::Name,
b"email" => IdentityProperty::Email,
b"replyTo" => IdentityProperty::ReplyTo,
b"bcc" => IdentityProperty::Bcc,
b"textSignature" => IdentityProperty::TextSignature,
b"htmlSignature" => IdentityProperty::HtmlSignature,
b"mayDelete" => IdentityProperty::MayDelete,
)
.or_else(|| {
if allow_patch && value.contains('/') {
IdentityProperty::Pointer(JsonPointer::parse(value)).into()
} else {
None
}
})
}
fn patch_or_prop(&self) -> &IdentityProperty {
if let IdentityProperty::Pointer(ptr) = self
&& let Some(JsonPointerItem::Key(Key::Property(prop))) = ptr.last()
{
prop
} else {
self
}
}
}
impl FromStr for IdentityProperty {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
IdentityProperty::parse(s, false).ok_or(())
}
}
impl JmapObject for Identity {
type Property = IdentityProperty;
type Element = IdentityValue;
type Id = Id;
type Filter = ();
type Comparator = ();
type GetArguments = ();
type SetArguments<'de> = ();
type QueryArguments = ();
type CopyArguments = ();
type ParseArguments = ();
const ID_PROPERTY: Self::Property = IdentityProperty::Id;
}
impl From<Id> for IdentityValue {
fn from(id: Id) -> Self {
IdentityValue::Id(id)
}
}
impl JmapObjectId for IdentityValue {
fn as_id(&self) -> Option<Id> {
match self {
IdentityValue::Id(id) => Some(*id),
}
}
fn as_any_id(&self) -> Option<AnyId> {
match self {
IdentityValue::Id(id) => Some(AnyId::Id(*id)),
}
}
fn as_id_ref(&self) -> Option<&str> {
None
}
fn try_set_id(&mut self, new_id: AnyId) -> bool {
if let AnyId::Id(id) = new_id {
*self = IdentityValue::Id(id);
true
} else {
false
}
}
}
impl JmapObjectId for IdentityProperty {
fn as_id(&self) -> Option<Id> {
None
}
fn as_any_id(&self) -> Option<AnyId> {
None
}
fn as_id_ref(&self) -> Option<&str> {
None
}
fn try_set_id(&mut self, _: AnyId) -> bool {
false
}
}
+516
View File
@@ -0,0 +1,516 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
object::{
AnyId, JmapObject, JmapObjectId, JmapRight, JmapSharedObject, MaybeReference, parse_ref,
},
request::{deserialize::DeserializeArguments, reference::MaybeIdReference},
};
use jmap_tools::{Element, JsonPointer, JsonPointerItem, Key, Property};
use std::{borrow::Cow, str::FromStr};
use types::{acl::Acl, id::Id, special_use::SpecialUse};
#[derive(Debug, Clone, Default)]
pub struct Mailbox;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum MailboxProperty {
Id,
Name,
ParentId,
Role,
SortOrder,
TotalEmails,
UnreadEmails,
TotalThreads,
UnreadThreads,
ShareWith,
MyRights,
IsSubscribed,
// Other
IdValue(Id),
Rights(MailboxRight),
Pointer(JsonPointer<MailboxProperty>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum MailboxRight {
MayReadItems,
MayAddItems,
MayRemoveItems,
MaySetSeen,
MaySetKeywords,
MayCreateChild,
MayRename,
MaySubmit,
MayDelete,
MayShare,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum MailboxValue {
Id(Id),
IdReference(String),
Role(SpecialUse),
}
impl Property for MailboxProperty {
fn try_parse(key: Option<&Key<'_, Self>>, value: &str) -> Option<Self> {
let allow_patch = key.is_none();
if let Some(Key::Property(key)) = key {
match key.patch_or_prop() {
MailboxProperty::ShareWith => {
Id::from_str(value).ok().map(MailboxProperty::IdValue)
}
_ => MailboxProperty::parse(value, allow_patch),
}
} else {
MailboxProperty::parse(value, allow_patch)
}
}
fn to_cow(&self) -> Cow<'static, str> {
match self {
MailboxProperty::Id => "id",
MailboxProperty::IsSubscribed => "isSubscribed",
MailboxProperty::MyRights => "myRights",
MailboxProperty::Name => "name",
MailboxProperty::ParentId => "parentId",
MailboxProperty::Role => "role",
MailboxProperty::SortOrder => "sortOrder",
MailboxProperty::TotalEmails => "totalEmails",
MailboxProperty::TotalThreads => "totalThreads",
MailboxProperty::UnreadEmails => "unreadEmails",
MailboxProperty::UnreadThreads => "unreadThreads",
MailboxProperty::ShareWith => "shareWith",
MailboxProperty::Rights(mailbox_right) => mailbox_right.as_str(),
MailboxProperty::Pointer(json_pointer) => return json_pointer.to_string().into(),
MailboxProperty::IdValue(id) => return id.to_string().into(),
}
.into()
}
}
impl MailboxRight {
pub fn as_str(&self) -> &'static str {
match self {
MailboxRight::MayReadItems => "mayReadItems",
MailboxRight::MayAddItems => "mayAddItems",
MailboxRight::MayRemoveItems => "mayRemoveItems",
MailboxRight::MaySetSeen => "maySetSeen",
MailboxRight::MaySetKeywords => "maySetKeywords",
MailboxRight::MayCreateChild => "mayCreateChild",
MailboxRight::MayRename => "mayRename",
MailboxRight::MaySubmit => "maySubmit",
MailboxRight::MayDelete => "mayDelete",
MailboxRight::MayShare => "mayShare",
}
}
}
impl Element for MailboxValue {
type Property = MailboxProperty;
fn try_parse<P>(key: &Key<'_, Self::Property>, value: &str) -> Option<Self> {
if let Key::Property(prop) = key {
match prop.patch_or_prop() {
MailboxProperty::Id | MailboxProperty::ParentId => match parse_ref(value) {
MaybeReference::Value(v) => Some(MailboxValue::Id(v)),
MaybeReference::Reference(v) => Some(MailboxValue::IdReference(v)),
MaybeReference::ParseError => None,
},
MailboxProperty::Role => SpecialUse::parse(value).map(MailboxValue::Role),
_ => None,
}
} else {
None
}
}
fn to_cow(&self) -> Cow<'static, str> {
match self {
MailboxValue::Id(id) => id.to_string().into(),
MailboxValue::IdReference(r) => format!("#{r}").into(),
MailboxValue::Role(special_use) => special_use.as_str().unwrap_or_default().into(),
}
}
}
impl MailboxProperty {
fn parse(value: &str, allow_patch: bool) -> Option<Self> {
hashify::tiny_map!(value.as_bytes(),
b"id" => MailboxProperty::Id,
b"name" => MailboxProperty::Name,
b"parentId" => MailboxProperty::ParentId,
b"role" => MailboxProperty::Role,
b"sortOrder" => MailboxProperty::SortOrder,
b"totalEmails" => MailboxProperty::TotalEmails,
b"unreadEmails" => MailboxProperty::UnreadEmails,
b"totalThreads" => MailboxProperty::TotalThreads,
b"unreadThreads" => MailboxProperty::UnreadThreads,
b"shareWith" => MailboxProperty::ShareWith,
b"myRights" => MailboxProperty::MyRights,
b"mayReadItems" => MailboxProperty::Rights(MailboxRight::MayReadItems),
b"mayAddItems" => MailboxProperty::Rights(MailboxRight::MayAddItems),
b"mayRemoveItems" => MailboxProperty::Rights(MailboxRight::MayRemoveItems),
b"maySetSeen" => MailboxProperty::Rights(MailboxRight::MaySetSeen),
b"maySetKeywords" => MailboxProperty::Rights(MailboxRight::MaySetKeywords),
b"mayCreateChild" => MailboxProperty::Rights(MailboxRight::MayCreateChild),
b"mayRename" => MailboxProperty::Rights(MailboxRight::MayRename),
b"maySubmit" => MailboxProperty::Rights(MailboxRight::MaySubmit),
b"mayDelete" => MailboxProperty::Rights(MailboxRight::MayDelete),
b"mayShare" => MailboxProperty::Rights(MailboxRight::MayShare),
b"isSubscribed" => MailboxProperty::IsSubscribed,
)
.or_else(|| {
if allow_patch && value.contains('/') {
MailboxProperty::Pointer(JsonPointer::parse(value)).into()
} else {
None
}
})
}
fn patch_or_prop(&self) -> &MailboxProperty {
if let MailboxProperty::Pointer(ptr) = self
&& let Some(JsonPointerItem::Key(Key::Property(prop))) = ptr.last()
{
prop
} else {
self
}
}
}
#[derive(Debug, Clone, Default)]
pub struct MailboxSetArguments {
pub on_destroy_remove_emails: Option<bool>,
}
#[derive(Debug, Clone, Default)]
pub struct MailboxQueryArguments {
pub sort_as_tree: Option<bool>,
pub filter_as_tree: Option<bool>,
}
impl<'de> DeserializeArguments<'de> for MailboxSetArguments {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'de>,
{
if key == "onDestroyRemoveEmails" {
self.on_destroy_remove_emails = map.next_value()?;
} else {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
Ok(())
}
}
impl<'de> DeserializeArguments<'de> for MailboxQueryArguments {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'de>,
{
hashify::fnc_map!(key.as_bytes(),
b"sortAsTree" => {
self.sort_as_tree = map.next_value()?;
},
b"filterAsTree" => {
self.filter_as_tree = map.next_value()?;
},
_ => {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
);
Ok(())
}
}
impl FromStr for MailboxProperty {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
MailboxProperty::parse(s, false).ok_or(())
}
}
impl JmapObject for Mailbox {
type Property = MailboxProperty;
type Element = MailboxValue;
type Id = Id;
type Filter = MailboxFilter;
type Comparator = MailboxComparator;
type GetArguments = ();
type SetArguments<'de> = MailboxSetArguments;
type QueryArguments = MailboxQueryArguments;
type CopyArguments = ();
type ParseArguments = ();
const ID_PROPERTY: Self::Property = MailboxProperty::Id;
}
impl JmapSharedObject for Mailbox {
type Right = MailboxRight;
const SHARE_WITH_PROPERTY: Self::Property = MailboxProperty::ShareWith;
}
impl From<Id> for MailboxProperty {
fn from(id: Id) -> Self {
MailboxProperty::IdValue(id)
}
}
impl TryFrom<MailboxProperty> for Id {
type Error = ();
fn try_from(value: MailboxProperty) -> Result<Self, Self::Error> {
if let MailboxProperty::IdValue(id) = value {
Ok(id)
} else {
Err(())
}
}
}
impl TryFrom<MailboxProperty> for MailboxRight {
type Error = ();
fn try_from(value: MailboxProperty) -> Result<Self, Self::Error> {
if let MailboxProperty::Rights(right) = value {
Ok(right)
} else {
Err(())
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MailboxFilter {
Name(String),
ParentId(Option<MaybeIdReference<Id>>),
Role(Option<SpecialUse>),
HasAnyRole(bool),
IsSubscribed(bool),
_T(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MailboxComparator {
SortOrder,
Name,
ParentId,
_T(String),
}
impl<'de> DeserializeArguments<'de> for MailboxFilter {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'de>,
{
hashify::fnc_map!(key.as_bytes(),
b"name" => {
*self = MailboxFilter::Name(map.next_value()?);
},
b"parentId" => {
*self = MailboxFilter::ParentId(map.next_value()?);
},
b"role" => {
*self = MailboxFilter::Role(map.next_value::<Option<RoleWrapper>>()?.map(|r| r.0));
},
b"hasAnyRole" => {
*self = MailboxFilter::HasAnyRole(map.next_value()?);
},
b"isSubscribed" => {
*self = MailboxFilter::IsSubscribed(map.next_value()?);
},
_ => {
*self = MailboxFilter::_T(key.to_string());
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
);
Ok(())
}
}
impl<'de> DeserializeArguments<'de> for MailboxComparator {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'de>,
{
if key == "property" {
let value = map.next_value::<Cow<str>>()?;
hashify::fnc_map!(value.as_bytes(),
b"sortOrder" => {
*self = MailboxComparator::SortOrder;
},
b"name" => {
*self = MailboxComparator::Name;
},
b"parentId" => {
*self = MailboxComparator::ParentId;
},
_ => {
*self = MailboxComparator::_T(key.to_string());
}
);
} else {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
Ok(())
}
}
impl Default for MailboxFilter {
fn default() -> Self {
MailboxFilter::_T("".to_string())
}
}
impl Default for MailboxComparator {
fn default() -> Self {
MailboxComparator::_T("".to_string())
}
}
struct RoleWrapper(SpecialUse);
impl<'de> serde::Deserialize<'de> for RoleWrapper {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
SpecialUse::parse(<&str>::deserialize(deserializer)?)
.map(RoleWrapper)
.ok_or_else(|| serde::de::Error::custom("invalid JMAP role"))
}
}
impl From<Id> for MailboxValue {
fn from(id: Id) -> Self {
MailboxValue::Id(id)
}
}
impl JmapObjectId for MailboxValue {
fn as_id(&self) -> Option<Id> {
if let MailboxValue::Id(id) = self {
Some(*id)
} else {
None
}
}
fn as_any_id(&self) -> Option<AnyId> {
if let MailboxValue::Id(id) = self {
Some(AnyId::Id(*id))
} else {
None
}
}
fn as_id_ref(&self) -> Option<&str> {
if let MailboxValue::IdReference(r) = self {
Some(r)
} else {
None
}
}
fn try_set_id(&mut self, new_id: AnyId) -> bool {
if let AnyId::Id(id) = new_id {
*self = MailboxValue::Id(id);
true
} else {
false
}
}
}
impl JmapRight for MailboxRight {
fn to_acl(&self) -> &'static [Acl] {
match self {
MailboxRight::MayReadItems => &[Acl::Read, Acl::ReadItems],
MailboxRight::MayAddItems => &[Acl::AddItems],
MailboxRight::MayRemoveItems => &[Acl::RemoveItems],
MailboxRight::MaySetSeen => &[Acl::ModifyItems],
MailboxRight::MaySetKeywords => &[Acl::ModifyItems],
MailboxRight::MayCreateChild => &[Acl::CreateChild],
MailboxRight::MayRename => &[Acl::Modify],
MailboxRight::MaySubmit => &[Acl::Submit],
MailboxRight::MayDelete => &[Acl::Delete],
MailboxRight::MayShare => &[Acl::Share],
}
}
fn all_rights() -> &'static [Self] {
&[
MailboxRight::MayReadItems,
MailboxRight::MayAddItems,
MailboxRight::MayRemoveItems,
MailboxRight::MaySetSeen,
MailboxRight::MaySetKeywords,
MailboxRight::MayCreateChild,
MailboxRight::MayRename,
MailboxRight::MaySubmit,
MailboxRight::MayDelete,
MailboxRight::MayShare,
]
}
}
impl From<MailboxRight> for MailboxProperty {
fn from(right: MailboxRight) -> Self {
MailboxProperty::Rights(right)
}
}
impl JmapObjectId for MailboxProperty {
fn as_id(&self) -> Option<Id> {
if let MailboxProperty::IdValue(id) = self {
Some(*id)
} else {
None
}
}
fn as_any_id(&self) -> Option<AnyId> {
if let MailboxProperty::IdValue(id) = self {
Some(AnyId::Id(*id))
} else {
None
}
}
fn as_id_ref(&self) -> Option<&str> {
None
}
fn try_set_id(&mut self, new_id: AnyId) -> bool {
if let AnyId::Id(id) = new_id {
*self = MailboxProperty::IdValue(id);
true
} else {
false
}
}
}
+211
View File
@@ -0,0 +1,211 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::request::deserialize::DeserializeArguments;
use jmap_tools::{Element, Null, Property};
use serde::Serialize;
use std::{fmt::Debug, str::FromStr};
use types::{acl::Acl, blob::BlobId, id::Id};
pub mod addressbook;
pub mod blob;
pub mod calendar;
pub mod calendar_event;
pub mod calendar_event_notification;
pub mod contact;
pub mod email;
pub mod email_submission;
pub mod file_node;
pub mod identity;
pub mod mailbox;
pub mod participant_identity;
pub mod principal;
pub mod push_subscription;
pub mod quota;
pub mod registry;
pub mod search_snippet;
pub mod share_notification;
pub mod sieve;
pub mod thread;
pub mod vacation_response;
pub trait JmapObject: std::fmt::Debug {
type Property: Property + JmapObjectId + FromStr + Debug + Sync + Send;
type Element: Element<Property = Self::Property> + JmapObjectId + Debug + Sync + Send;
type Id: FromStr + TryFrom<AnyId> + Into<Self::Element> + Serialize + Debug + Sync + Send;
type Filter: Default + for<'de> DeserializeArguments<'de> + Debug + Sync + Send;
type Comparator: Default + for<'de> DeserializeArguments<'de> + Debug + Sync + Send;
type GetArguments: Default + for<'de> DeserializeArguments<'de> + Debug + Sync + Send;
type SetArguments<'de>: Default + DeserializeArguments<'de> + Debug + Sync + Send;
type QueryArguments: Default + for<'de> DeserializeArguments<'de> + Debug + Sync + Send;
type CopyArguments: Default + for<'de> DeserializeArguments<'de> + Debug + Sync + Send;
type ParseArguments: Default + for<'de> DeserializeArguments<'de> + Debug + Sync + Send;
const ID_PROPERTY: Self::Property;
}
pub trait JmapSharedObject: JmapObject {
type Right: JmapRight + Into<Self::Property> + Debug + Clone + Copy + Sync + Send;
const SHARE_WITH_PROPERTY: Self::Property;
}
pub trait JmapRight: Clone + Copy + Sized + 'static {
fn all_rights() -> &'static [Self];
fn to_acl(&self) -> &'static [Acl];
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(untagged)]
pub enum AnyId {
Id(Id),
BlobId(BlobId),
}
pub trait JmapObjectId {
fn as_id(&self) -> Option<Id>;
fn as_any_id(&self) -> Option<AnyId>;
fn as_id_ref(&self) -> Option<&str>;
fn try_set_id(&mut self, new_id: AnyId) -> bool;
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum MaybeReference<T: FromStr> {
Value(T),
Reference(String),
ParseError,
}
fn parse_ref<T: FromStr>(value: &str) -> MaybeReference<T> {
if let Some(reference) = value.strip_prefix('#') {
MaybeReference::Reference(reference.to_string())
} else {
T::from_str(value)
.map(MaybeReference::Value)
.unwrap_or(MaybeReference::ParseError)
}
}
impl From<Id> for AnyId {
fn from(value: Id) -> Self {
AnyId::Id(value)
}
}
impl From<BlobId> for AnyId {
fn from(value: BlobId) -> Self {
AnyId::BlobId(value)
}
}
impl TryFrom<AnyId> for Id {
type Error = ();
fn try_from(value: AnyId) -> Result<Self, Self::Error> {
if let AnyId::Id(id) = value {
Ok(id)
} else {
Err(())
}
}
}
impl TryFrom<AnyId> for BlobId {
type Error = ();
fn try_from(value: AnyId) -> Result<Self, Self::Error> {
if let AnyId::BlobId(id) = value {
Ok(id)
} else {
Err(())
}
}
}
impl<'de> serde::Deserialize<'de> for AnyId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = <&str>::deserialize(deserializer)?;
if let Some(blob_id) = BlobId::from_base32(value) {
Ok(AnyId::BlobId(blob_id))
} else if let Ok(id) = Id::from_str(value) {
Ok(AnyId::Id(id))
} else {
Err(serde::de::Error::custom(format!(
"Invalid AnyId: {}",
value
)))
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
pub struct NullObject;
impl JmapObject for NullObject {
type Property = Null;
type Element = Null;
type Id = Null;
type Filter = ();
type Comparator = ();
type GetArguments = ();
type SetArguments<'de> = ();
type QueryArguments = ();
type CopyArguments = ();
type ParseArguments = ();
const ID_PROPERTY: Self::Property = Null;
}
impl JmapRight for Null {
fn all_rights() -> &'static [Self] {
unreachable!()
}
fn to_acl(&self) -> &'static [Acl] {
unreachable!()
}
}
impl FromStr for NullObject {
type Err = ();
fn from_str(_: &str) -> Result<Self, Self::Err> {
unreachable!()
}
}
impl JmapObjectId for Null {
fn as_id(&self) -> Option<Id> {
unreachable!()
}
fn as_any_id(&self) -> Option<AnyId> {
unreachable!()
}
fn as_id_ref(&self) -> Option<&str> {
unreachable!()
}
fn try_set_id(&mut self, _: AnyId) -> bool {
unreachable!()
}
}
impl TryFrom<AnyId> for Null {
type Error = ();
fn try_from(_: AnyId) -> Result<Self, Self::Error> {
unreachable!()
}
}
@@ -0,0 +1,205 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
object::{AnyId, JmapObject, JmapObjectId},
request::{deserialize::DeserializeArguments, reference::MaybeIdReference},
};
use jmap_tools::{Element, Key, Property};
use std::{borrow::Cow, fmt::Display, str::FromStr};
use types::id::Id;
#[derive(Debug, Clone, Default)]
pub struct ParticipantIdentity;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ParticipantIdentityProperty {
Id,
Name,
CalendarAddress,
IsDefault,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ParticipantIdentityValue {
Id(Id),
}
impl Property for ParticipantIdentityProperty {
fn try_parse(_: Option<&Key<'_, Self>>, value: &str) -> Option<Self> {
ParticipantIdentityProperty::parse(value)
}
fn to_cow(&self) -> Cow<'static, str> {
match self {
ParticipantIdentityProperty::Id => "id",
ParticipantIdentityProperty::Name => "name",
ParticipantIdentityProperty::CalendarAddress => "calendarAddress",
ParticipantIdentityProperty::IsDefault => "isDefault",
}
.into()
}
}
impl Element for ParticipantIdentityValue {
type Property = ParticipantIdentityProperty;
fn try_parse<P>(key: &Key<'_, Self::Property>, value: &str) -> Option<Self> {
if let Key::Property(prop) = key {
match prop {
ParticipantIdentityProperty::Id => {
Id::from_str(value).ok().map(ParticipantIdentityValue::Id)
}
_ => None,
}
} else {
None
}
}
fn to_cow(&self) -> Cow<'static, str> {
match self {
ParticipantIdentityValue::Id(id) => id.to_string().into(),
}
}
}
impl ParticipantIdentityProperty {
fn parse(value: &str) -> Option<Self> {
hashify::tiny_map!(value.as_bytes(),
b"id" => ParticipantIdentityProperty::Id,
b"name" => ParticipantIdentityProperty::Name,
b"calendarAddress" => ParticipantIdentityProperty::CalendarAddress,
b"isDefault" => ParticipantIdentityProperty::IsDefault
)
}
fn as_str(&self) -> &'static str {
match self {
ParticipantIdentityProperty::Id => "id",
ParticipantIdentityProperty::Name => "name",
ParticipantIdentityProperty::CalendarAddress => "calendarAddress",
ParticipantIdentityProperty::IsDefault => "isDefault",
}
}
}
#[derive(Debug, Clone, Default)]
pub struct ParticipantIdentitySetArguments {
pub on_success_set_is_default: Option<MaybeIdReference<Id>>,
}
impl<'de> DeserializeArguments<'de> for ParticipantIdentitySetArguments {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'de>,
{
hashify::fnc_map!(key.as_bytes(),
b"onSuccessSetIsDefault" => {
self.on_success_set_is_default = map.next_value()?;
},
_ => {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
);
Ok(())
}
}
impl FromStr for ParticipantIdentityProperty {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
ParticipantIdentityProperty::parse(s).ok_or(())
}
}
impl JmapObject for ParticipantIdentity {
type Property = ParticipantIdentityProperty;
type Element = ParticipantIdentityValue;
type Id = Id;
type Filter = ();
type Comparator = ();
type GetArguments = ();
type SetArguments<'de> = ParticipantIdentitySetArguments;
type QueryArguments = ();
type CopyArguments = ();
type ParseArguments = ();
const ID_PROPERTY: Self::Property = ParticipantIdentityProperty::Id;
}
impl TryFrom<ParticipantIdentityProperty> for Id {
type Error = ();
fn try_from(_: ParticipantIdentityProperty) -> Result<Self, Self::Error> {
Err(())
}
}
impl From<Id> for ParticipantIdentityValue {
fn from(id: Id) -> Self {
ParticipantIdentityValue::Id(id)
}
}
impl JmapObjectId for ParticipantIdentityValue {
fn as_id(&self) -> Option<Id> {
let ParticipantIdentityValue::Id(id) = self;
Some(*id)
}
fn as_any_id(&self) -> Option<AnyId> {
let ParticipantIdentityValue::Id(id) = self;
Some(AnyId::Id(*id))
}
fn as_id_ref(&self) -> Option<&str> {
None
}
fn try_set_id(&mut self, new_id: AnyId) -> bool {
if let AnyId::Id(new_id) = new_id {
*self = ParticipantIdentityValue::Id(new_id);
return true;
}
false
}
}
impl JmapObjectId for ParticipantIdentityProperty {
fn as_id(&self) -> Option<Id> {
None
}
fn as_any_id(&self) -> Option<AnyId> {
None
}
fn as_id_ref(&self) -> Option<&str> {
None
}
fn try_set_id(&mut self, _: AnyId) -> bool {
false
}
}
impl Display for ParticipantIdentityProperty {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.as_str().fmt(f)
}
}
+354
View File
@@ -0,0 +1,354 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use jmap_tools::{Element, Key, Property};
use std::{borrow::Cow, fmt::Display, str::FromStr};
use types::id::Id;
use crate::{
object::{AnyId, JmapObject, JmapObjectId},
request::{capability::Capability, deserialize::DeserializeArguments},
};
#[derive(Debug, Clone, Default)]
pub struct Principal;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum PrincipalProperty {
Id,
Type,
Name,
Description,
Email,
Timezone,
Capabilities,
Accounts,
IdValue(Id),
Capability(Capability),
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum PrincipalValue {
Id(Id),
Type(PrincipalType),
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum PrincipalType {
Individual,
Group,
Resource,
Location,
Other,
}
impl Property for PrincipalProperty {
fn try_parse(_: Option<&Key<'_, Self>>, value: &str) -> Option<Self> {
PrincipalProperty::parse(value)
}
fn to_cow(&self) -> Cow<'static, str> {
match self {
PrincipalProperty::Capabilities => "capabilities",
PrincipalProperty::Description => "description",
PrincipalProperty::Email => "email",
PrincipalProperty::Id => "id",
PrincipalProperty::Name => "name",
PrincipalProperty::Timezone => "timezone",
PrincipalProperty::Type => "type",
PrincipalProperty::Accounts => "accounts",
PrincipalProperty::Capability(cap) => cap.as_str(),
PrincipalProperty::IdValue(id) => return id.to_string().into(),
}
.into()
}
}
impl Element for PrincipalValue {
type Property = PrincipalProperty;
fn try_parse<P>(key: &Key<'_, Self::Property>, value: &str) -> Option<Self> {
if let Key::Property(prop) = key {
match prop {
PrincipalProperty::Id => Id::from_str(value).ok().map(PrincipalValue::Id),
PrincipalProperty::Type => PrincipalType::parse(value).map(PrincipalValue::Type),
_ => None,
}
} else {
None
}
}
fn to_cow(&self) -> Cow<'static, str> {
match self {
PrincipalValue::Id(id) => id.to_string().into(),
PrincipalValue::Type(t) => t.as_str().into(),
}
}
}
impl PrincipalProperty {
pub fn parse(value: &str) -> Option<Self> {
hashify::tiny_map!(value.as_bytes(),
b"id" => PrincipalProperty::Id,
b"type" => PrincipalProperty::Type,
b"name" => PrincipalProperty::Name,
b"description" => PrincipalProperty::Description,
b"email" => PrincipalProperty::Email,
b"timeZone" => PrincipalProperty::Timezone,
b"capabilities" => PrincipalProperty::Capabilities,
b"accounts" => PrincipalProperty::Accounts,
)
}
pub fn as_str(&self) -> &'static str {
match self {
PrincipalProperty::Id => "id",
PrincipalProperty::Type => "type",
PrincipalProperty::Name => "name",
PrincipalProperty::Description => "description",
PrincipalProperty::Email => "email",
PrincipalProperty::Timezone => "timeZone",
PrincipalProperty::Capabilities => "capabilities",
PrincipalProperty::Accounts => "accounts",
PrincipalProperty::Capability(cap) => cap.as_str(),
PrincipalProperty::IdValue(_) => "",
}
}
}
impl PrincipalType {
pub fn parse(s: &str) -> Option<Self> {
hashify::tiny_map!(s.as_bytes(),
b"individual" => PrincipalType::Individual,
b"group" => PrincipalType::Group,
b"resource" => PrincipalType::Resource,
b"location" => PrincipalType::Location,
b"other" => PrincipalType::Other,
)
}
pub fn as_str(&self) -> &'static str {
match self {
PrincipalType::Individual => "individual",
PrincipalType::Group => "group",
PrincipalType::Resource => "resource",
PrincipalType::Location => "location",
PrincipalType::Other => "other",
}
}
}
impl FromStr for PrincipalProperty {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
PrincipalProperty::parse(s).ok_or(())
}
}
impl JmapObject for Principal {
type Property = PrincipalProperty;
type Element = PrincipalValue;
type Id = Id;
type Filter = PrincipalFilter;
type Comparator = PrincipalComparator;
type GetArguments = ();
type SetArguments<'de> = ();
type QueryArguments = ();
type CopyArguments = ();
type ParseArguments = ();
const ID_PROPERTY: Self::Property = PrincipalProperty::Id;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PrincipalFilter {
AccountIds(Vec<Id>),
Email(String),
Name(String),
Text(String),
Type(PrincipalType),
Timezone(String),
_T(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PrincipalComparator {
Name,
Email,
Type,
_T(String),
}
impl<'de> DeserializeArguments<'de> for PrincipalFilter {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'de>,
{
hashify::fnc_map!(key.as_bytes(),
b"accountIds" => {
*self = PrincipalFilter::AccountIds(map.next_value()?);
},
b"email" => {
*self = PrincipalFilter::Email(map.next_value()?);
},
b"name" => {
*self = PrincipalFilter::Name(map.next_value()?);
},
b"text" => {
*self = PrincipalFilter::Text(map.next_value()?);
},
b"type" => {
*self = PrincipalFilter::Type(map.next_value()?);
},
b"timeZone" => {
*self = PrincipalFilter::Timezone(map.next_value()?);
},
_ => {
*self = PrincipalFilter::_T(key.to_string());
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
);
Ok(())
}
}
impl<'de> DeserializeArguments<'de> for PrincipalComparator {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'de>,
{
if key == "property" {
let value = map.next_value::<Cow<str>>()?;
hashify::fnc_map!(value.as_bytes(),
b"name" => {
*self = PrincipalComparator::Name;
},
b"email" => {
*self = PrincipalComparator::Email;
},
b"type" => {
*self = PrincipalComparator::Type;
},
_ => {
*self = PrincipalComparator::_T(key.to_string());
}
);
} else {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
Ok(())
}
}
impl Default for PrincipalFilter {
fn default() -> Self {
PrincipalFilter::_T("".to_string())
}
}
impl Default for PrincipalComparator {
fn default() -> Self {
PrincipalComparator::_T("".to_string())
}
}
impl<'de> serde::Deserialize<'de> for PrincipalType {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
PrincipalType::parse(<&str>::deserialize(deserializer)?)
.ok_or_else(|| serde::de::Error::custom("invalid JMAP PrincipalType"))
}
}
impl From<Id> for PrincipalValue {
fn from(id: Id) -> Self {
PrincipalValue::Id(id)
}
}
impl JmapObjectId for PrincipalValue {
fn as_id(&self) -> Option<Id> {
if let PrincipalValue::Id(id) = self {
Some(*id)
} else {
None
}
}
fn as_any_id(&self) -> Option<AnyId> {
if let PrincipalValue::Id(id) = self {
Some(AnyId::Id(*id))
} else {
None
}
}
fn as_id_ref(&self) -> Option<&str> {
None
}
fn try_set_id(&mut self, new_id: AnyId) -> bool {
if let AnyId::Id(id) = new_id {
*self = PrincipalValue::Id(id);
true
} else {
false
}
}
}
impl Display for PrincipalFilter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
PrincipalFilter::AccountIds(_) => "accountIds",
PrincipalFilter::Email(_) => "email",
PrincipalFilter::Name(_) => "name",
PrincipalFilter::Text(_) => "text",
PrincipalFilter::Type(_) => "type",
PrincipalFilter::Timezone(_) => "timezone",
PrincipalFilter::_T(other) => other,
})
}
}
impl JmapObjectId for PrincipalProperty {
fn as_id(&self) -> Option<Id> {
None
}
fn as_any_id(&self) -> Option<AnyId> {
None
}
fn as_id_ref(&self) -> Option<&str> {
None
}
fn try_set_id(&mut self, _: AnyId) -> bool {
false
}
}
impl Display for PrincipalProperty {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
@@ -0,0 +1,429 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::object::email::{EmailProperty, HeaderForm, HeaderProperty};
use crate::object::{AnyId, JmapObject, JmapObjectId};
use crate::types::date::UTCDate;
use jmap_tools::{Element, JsonPointer, JsonPointerItem};
use jmap_tools::{Key, Property};
use std::borrow::Cow;
use std::str::FromStr;
use types::{id::Id, type_state::DataType};
#[derive(Debug, Clone, Default)]
pub struct PushSubscription;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum PushSubscriptionProperty {
Id,
DeviceClientId,
Url,
Keys,
P256dh,
Auth,
VerificationCode,
Expires,
Types,
EmailPush,
// Other
Pointer(JsonPointer<PushSubscriptionProperty>),
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum PushSubscriptionValue {
Id(Id),
Date(UTCDate),
Types(DataType),
}
impl Property for PushSubscriptionProperty {
fn try_parse(key: Option<&Key<'_, Self>>, value: &str) -> Option<Self> {
PushSubscriptionProperty::parse(value, key.is_none())
}
fn to_cow(&self) -> Cow<'static, str> {
match self {
PushSubscriptionProperty::DeviceClientId => "deviceClientId",
PushSubscriptionProperty::Expires => "expires",
PushSubscriptionProperty::Id => "id",
PushSubscriptionProperty::Keys => "keys",
PushSubscriptionProperty::Types => "types",
PushSubscriptionProperty::Url => "url",
PushSubscriptionProperty::EmailPush => "emailPush",
PushSubscriptionProperty::VerificationCode => "verificationCode",
PushSubscriptionProperty::P256dh => "p256dh",
PushSubscriptionProperty::Auth => "auth",
PushSubscriptionProperty::Pointer(json_pointer) => {
return json_pointer.to_string().into();
}
}
.into()
}
}
impl PushSubscriptionProperty {
fn parse(value: &str, allow_patch: bool) -> Option<Self> {
hashify::tiny_map!(value.as_bytes(),
b"id" => PushSubscriptionProperty::Id,
b"deviceClientId" => PushSubscriptionProperty::DeviceClientId,
b"url" => PushSubscriptionProperty::Url,
b"keys" => PushSubscriptionProperty::Keys,
b"p256dh" => PushSubscriptionProperty::P256dh,
b"auth" => PushSubscriptionProperty::Auth,
b"verificationCode" => PushSubscriptionProperty::VerificationCode,
b"expires" => PushSubscriptionProperty::Expires,
b"types" => PushSubscriptionProperty::Types,
b"emailPush" => PushSubscriptionProperty::EmailPush,
)
.or_else(|| {
if allow_patch && value.contains('/') {
PushSubscriptionProperty::Pointer(JsonPointer::parse(value)).into()
} else {
None
}
})
}
fn patch_or_prop(&self) -> &PushSubscriptionProperty {
if let PushSubscriptionProperty::Pointer(ptr) = self
&& let Some(JsonPointerItem::Key(Key::Property(prop))) = ptr.last()
{
prop
} else {
self
}
}
}
impl Element for PushSubscriptionValue {
type Property = PushSubscriptionProperty;
fn try_parse<P>(key: &Key<'_, Self::Property>, value: &str) -> Option<Self> {
if let Key::Property(prop) = key {
match prop.patch_or_prop() {
PushSubscriptionProperty::Id => {
Id::from_str(value).ok().map(PushSubscriptionValue::Id)
}
PushSubscriptionProperty::Types => {
DataType::parse(value).map(PushSubscriptionValue::Types)
}
PushSubscriptionProperty::Expires => UTCDate::from_str(value)
.ok()
.map(PushSubscriptionValue::Date),
_ => None,
}
} else {
None
}
}
fn to_cow(&self) -> Cow<'static, str> {
match self {
PushSubscriptionValue::Id(id) => id.to_string().into(),
PushSubscriptionValue::Date(utcdate) => utcdate.to_string().into(),
PushSubscriptionValue::Types(data_type) => data_type.as_str().into(),
}
}
}
impl FromStr for PushSubscriptionProperty {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
PushSubscriptionProperty::parse(s, false).ok_or(())
}
}
#[derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Clone, PartialEq, Eq)]
pub enum EmailPushProperty {
Id,
BlobId,
ThreadId,
MailboxIds,
Keywords,
Size,
ReceivedAt,
MessageId,
InReplyTo,
References,
Sender,
From,
To,
Cc,
Bcc,
ReplyTo,
Subject,
SentAt,
Preview,
HasAttachment,
BodyStructure,
BodyValues,
TextBody,
HtmlBody,
Attachments,
Headers,
Header(EmailPushHeaderProperty),
}
#[derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Clone, PartialEq, Eq)]
pub struct EmailPushHeaderProperty {
pub form: EmailPushHeaderForm,
pub header: String,
pub all: bool,
}
#[derive(
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Clone, Copy, PartialEq, Eq, Default,
)]
#[rkyv(compare(PartialEq), derive(Debug))]
#[repr(u8)]
pub enum EmailPushHeaderForm {
#[default]
Raw = 0,
Text = 1,
Addresses = 2,
GroupedAddresses = 3,
MessageIds = 4,
Date = 5,
Urls = 6,
}
impl TryFrom<&EmailProperty> for EmailPushProperty {
type Error = ();
fn try_from(value: &EmailProperty) -> Result<Self, Self::Error> {
Ok(match value {
EmailProperty::Id => EmailPushProperty::Id,
EmailProperty::BlobId => EmailPushProperty::BlobId,
EmailProperty::ThreadId => EmailPushProperty::ThreadId,
EmailProperty::MailboxIds => EmailPushProperty::MailboxIds,
EmailProperty::Keywords => EmailPushProperty::Keywords,
EmailProperty::Size => EmailPushProperty::Size,
EmailProperty::ReceivedAt => EmailPushProperty::ReceivedAt,
EmailProperty::MessageId => EmailPushProperty::MessageId,
EmailProperty::InReplyTo => EmailPushProperty::InReplyTo,
EmailProperty::References => EmailPushProperty::References,
EmailProperty::Sender => EmailPushProperty::Sender,
EmailProperty::From => EmailPushProperty::From,
EmailProperty::To => EmailPushProperty::To,
EmailProperty::Cc => EmailPushProperty::Cc,
EmailProperty::Bcc => EmailPushProperty::Bcc,
EmailProperty::ReplyTo => EmailPushProperty::ReplyTo,
EmailProperty::Subject => EmailPushProperty::Subject,
EmailProperty::SentAt => EmailPushProperty::SentAt,
EmailProperty::Preview => EmailPushProperty::Preview,
EmailProperty::HasAttachment => EmailPushProperty::HasAttachment,
EmailProperty::BodyStructure => EmailPushProperty::BodyStructure,
EmailProperty::BodyValues => EmailPushProperty::BodyValues,
EmailProperty::TextBody => EmailPushProperty::TextBody,
EmailProperty::HtmlBody => EmailPushProperty::HtmlBody,
EmailProperty::Attachments => EmailPushProperty::Attachments,
EmailProperty::Headers => EmailPushProperty::Headers,
EmailProperty::Header(header) => EmailPushProperty::Header(EmailPushHeaderProperty {
form: (&header.form).into(),
header: header.header.clone(),
all: header.all,
}),
_ => return Err(()),
})
}
}
impl From<&EmailPushProperty> for EmailProperty {
fn from(value: &EmailPushProperty) -> Self {
match value {
EmailPushProperty::Id => EmailProperty::Id,
EmailPushProperty::BlobId => EmailProperty::BlobId,
EmailPushProperty::ThreadId => EmailProperty::ThreadId,
EmailPushProperty::MailboxIds => EmailProperty::MailboxIds,
EmailPushProperty::Keywords => EmailProperty::Keywords,
EmailPushProperty::Size => EmailProperty::Size,
EmailPushProperty::ReceivedAt => EmailProperty::ReceivedAt,
EmailPushProperty::MessageId => EmailProperty::MessageId,
EmailPushProperty::InReplyTo => EmailProperty::InReplyTo,
EmailPushProperty::References => EmailProperty::References,
EmailPushProperty::Sender => EmailProperty::Sender,
EmailPushProperty::From => EmailProperty::From,
EmailPushProperty::To => EmailProperty::To,
EmailPushProperty::Cc => EmailProperty::Cc,
EmailPushProperty::Bcc => EmailProperty::Bcc,
EmailPushProperty::ReplyTo => EmailProperty::ReplyTo,
EmailPushProperty::Subject => EmailProperty::Subject,
EmailPushProperty::SentAt => EmailProperty::SentAt,
EmailPushProperty::Preview => EmailProperty::Preview,
EmailPushProperty::HasAttachment => EmailProperty::HasAttachment,
EmailPushProperty::BodyStructure => EmailProperty::BodyStructure,
EmailPushProperty::BodyValues => EmailProperty::BodyValues,
EmailPushProperty::TextBody => EmailProperty::TextBody,
EmailPushProperty::HtmlBody => EmailProperty::HtmlBody,
EmailPushProperty::Attachments => EmailProperty::Attachments,
EmailPushProperty::Headers => EmailProperty::Headers,
EmailPushProperty::Header(header) => EmailProperty::Header(HeaderProperty {
form: (&header.form).into(),
header: header.header.clone(),
all: header.all,
}),
}
}
}
impl From<&ArchivedEmailPushProperty> for EmailProperty {
fn from(value: &ArchivedEmailPushProperty) -> Self {
match value {
ArchivedEmailPushProperty::Id => EmailProperty::Id,
ArchivedEmailPushProperty::BlobId => EmailProperty::BlobId,
ArchivedEmailPushProperty::ThreadId => EmailProperty::ThreadId,
ArchivedEmailPushProperty::MailboxIds => EmailProperty::MailboxIds,
ArchivedEmailPushProperty::Keywords => EmailProperty::Keywords,
ArchivedEmailPushProperty::Size => EmailProperty::Size,
ArchivedEmailPushProperty::ReceivedAt => EmailProperty::ReceivedAt,
ArchivedEmailPushProperty::MessageId => EmailProperty::MessageId,
ArchivedEmailPushProperty::InReplyTo => EmailProperty::InReplyTo,
ArchivedEmailPushProperty::References => EmailProperty::References,
ArchivedEmailPushProperty::Sender => EmailProperty::Sender,
ArchivedEmailPushProperty::From => EmailProperty::From,
ArchivedEmailPushProperty::To => EmailProperty::To,
ArchivedEmailPushProperty::Cc => EmailProperty::Cc,
ArchivedEmailPushProperty::Bcc => EmailProperty::Bcc,
ArchivedEmailPushProperty::ReplyTo => EmailProperty::ReplyTo,
ArchivedEmailPushProperty::Subject => EmailProperty::Subject,
ArchivedEmailPushProperty::SentAt => EmailProperty::SentAt,
ArchivedEmailPushProperty::Preview => EmailProperty::Preview,
ArchivedEmailPushProperty::HasAttachment => EmailProperty::HasAttachment,
ArchivedEmailPushProperty::BodyStructure => EmailProperty::BodyStructure,
ArchivedEmailPushProperty::BodyValues => EmailProperty::BodyValues,
ArchivedEmailPushProperty::TextBody => EmailProperty::TextBody,
ArchivedEmailPushProperty::HtmlBody => EmailProperty::HtmlBody,
ArchivedEmailPushProperty::Attachments => EmailProperty::Attachments,
ArchivedEmailPushProperty::Headers => EmailProperty::Headers,
ArchivedEmailPushProperty::Header(header) => EmailProperty::Header(HeaderProperty {
form: (&header.form).into(),
header: header.header.as_str().to_string(),
all: header.all,
}),
}
}
}
impl From<&ArchivedEmailPushHeaderForm> for HeaderForm {
fn from(value: &ArchivedEmailPushHeaderForm) -> Self {
match value {
ArchivedEmailPushHeaderForm::Raw => HeaderForm::Raw,
ArchivedEmailPushHeaderForm::Text => HeaderForm::Text,
ArchivedEmailPushHeaderForm::Addresses => HeaderForm::Addresses,
ArchivedEmailPushHeaderForm::GroupedAddresses => HeaderForm::GroupedAddresses,
ArchivedEmailPushHeaderForm::MessageIds => HeaderForm::MessageIds,
ArchivedEmailPushHeaderForm::Date => HeaderForm::Date,
ArchivedEmailPushHeaderForm::Urls => HeaderForm::URLs,
}
}
}
impl From<&HeaderForm> for EmailPushHeaderForm {
fn from(value: &HeaderForm) -> Self {
match value {
HeaderForm::Raw => EmailPushHeaderForm::Raw,
HeaderForm::Text => EmailPushHeaderForm::Text,
HeaderForm::Addresses => EmailPushHeaderForm::Addresses,
HeaderForm::GroupedAddresses => EmailPushHeaderForm::GroupedAddresses,
HeaderForm::MessageIds => EmailPushHeaderForm::MessageIds,
HeaderForm::Date => EmailPushHeaderForm::Date,
HeaderForm::URLs => EmailPushHeaderForm::Urls,
}
}
}
impl From<&EmailPushHeaderForm> for HeaderForm {
fn from(value: &EmailPushHeaderForm) -> Self {
match value {
EmailPushHeaderForm::Raw => HeaderForm::Raw,
EmailPushHeaderForm::Text => HeaderForm::Text,
EmailPushHeaderForm::Addresses => HeaderForm::Addresses,
EmailPushHeaderForm::GroupedAddresses => HeaderForm::GroupedAddresses,
EmailPushHeaderForm::MessageIds => HeaderForm::MessageIds,
EmailPushHeaderForm::Date => HeaderForm::Date,
EmailPushHeaderForm::Urls => HeaderForm::URLs,
}
}
}
impl JmapObject for PushSubscription {
type Property = PushSubscriptionProperty;
type Element = PushSubscriptionValue;
type Id = Id;
type Filter = ();
type Comparator = ();
type GetArguments = ();
type SetArguments<'de> = ();
type QueryArguments = ();
type CopyArguments = ();
type ParseArguments = ();
const ID_PROPERTY: Self::Property = PushSubscriptionProperty::Id;
}
impl From<Id> for PushSubscriptionValue {
fn from(id: Id) -> Self {
PushSubscriptionValue::Id(id)
}
}
impl JmapObjectId for PushSubscriptionValue {
fn as_id(&self) -> Option<Id> {
match self {
PushSubscriptionValue::Id(id) => Some(*id),
_ => None,
}
}
fn as_any_id(&self) -> Option<AnyId> {
match self {
PushSubscriptionValue::Id(id) => Some(AnyId::Id(*id)),
_ => None,
}
}
fn as_id_ref(&self) -> Option<&str> {
None
}
fn try_set_id(&mut self, new_id: AnyId) -> bool {
if let AnyId::Id(id) = new_id {
*self = PushSubscriptionValue::Id(id);
true
} else {
false
}
}
}
impl JmapObjectId for PushSubscriptionProperty {
fn as_id(&self) -> Option<Id> {
None
}
fn as_any_id(&self) -> Option<AnyId> {
None
}
fn as_id_ref(&self) -> Option<&str> {
None
}
fn try_set_id(&mut self, _: AnyId) -> bool {
false
}
}
+267
View File
@@ -0,0 +1,267 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
object::{AnyId, JmapObject, JmapObjectId},
request::deserialize::DeserializeArguments,
};
use jmap_tools::{Element, Key, Property};
use std::{borrow::Cow, str::FromStr};
use types::{id::Id, type_state::DataType};
#[derive(Debug, Clone, Default)]
pub struct Quota;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum QuotaProperty {
Id,
ResourceType,
Used,
Name,
Scope,
Types,
HardLimit,
WarnLimit,
SoftLimit,
Description,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum QuotaValue {
Id(Id),
Types(DataType),
}
impl Property for QuotaProperty {
fn try_parse(_: Option<&Key<'_, Self>>, value: &str) -> Option<Self> {
QuotaProperty::parse(value)
}
fn to_cow(&self) -> Cow<'static, str> {
match self {
QuotaProperty::Description => "description",
QuotaProperty::Id => "id",
QuotaProperty::Name => "name",
QuotaProperty::Types => "types",
QuotaProperty::ResourceType => "resourceType",
QuotaProperty::Used => "used",
QuotaProperty::HardLimit => "hardLimit",
QuotaProperty::Scope => "scope",
QuotaProperty::WarnLimit => "warnLimit",
QuotaProperty::SoftLimit => "softLimit",
}
.into()
}
}
impl QuotaProperty {
fn parse(value: &str) -> Option<Self> {
hashify::tiny_map!(value.as_bytes(),
b"id" => QuotaProperty::Id,
b"resourceType" => QuotaProperty::ResourceType,
b"used" => QuotaProperty::Used,
b"name" => QuotaProperty::Name,
b"scope" => QuotaProperty::Scope,
b"types" => QuotaProperty::Types,
b"hardLimit" => QuotaProperty::HardLimit,
b"warnLimit" => QuotaProperty::WarnLimit,
b"softLimit" => QuotaProperty::SoftLimit,
b"description" => QuotaProperty::Description,
)
}
}
impl Element for QuotaValue {
type Property = QuotaProperty;
fn try_parse<P>(key: &Key<'_, Self::Property>, value: &str) -> Option<Self> {
if let Key::Property(prop) = key {
match prop {
QuotaProperty::Id => Id::from_str(value).ok().map(QuotaValue::Id),
QuotaProperty::Types => DataType::parse(value).map(QuotaValue::Types),
_ => None,
}
} else {
None
}
}
fn to_cow(&self) -> Cow<'static, str> {
match self {
QuotaValue::Id(id) => id.to_string().into(),
QuotaValue::Types(data_type) => data_type.as_str().into(),
}
}
}
impl FromStr for QuotaProperty {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
QuotaProperty::parse(s).ok_or(())
}
}
impl JmapObject for Quota {
type Property = QuotaProperty;
type Element = QuotaValue;
type Id = Id;
type Filter = QuotaFilter;
type Comparator = QuotaComparator;
type GetArguments = ();
type SetArguments<'de> = ();
type QueryArguments = ();
type CopyArguments = ();
type ParseArguments = ();
const ID_PROPERTY: Self::Property = QuotaProperty::Id;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum QuotaFilter {
Name(String),
Type(String),
Scope(String),
ResourceType(String),
_T(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum QuotaComparator {
Name,
Type,
Used,
_T(String),
}
impl<'de> DeserializeArguments<'de> for QuotaFilter {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'de>,
{
hashify::fnc_map!(key.as_bytes(),
b"name" => {
*self = QuotaFilter::Name(map.next_value()?);
},
b"type" => {
*self = QuotaFilter::Type(map.next_value()?);
},
b"scope" => {
*self = QuotaFilter::Scope(map.next_value()?);
},
b"resourceType" => {
*self = QuotaFilter::ResourceType(map.next_value()?);
},
_ => {
*self = QuotaFilter::_T(key.to_string());
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
);
Ok(())
}
}
impl<'de> DeserializeArguments<'de> for QuotaComparator {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'de>,
{
if key == "property" {
let value = map.next_value::<Cow<str>>()?;
hashify::fnc_map!(value.as_bytes(),
b"name" => {
*self = QuotaComparator::Name;
},
b"type" => {
*self = QuotaComparator::Type;
},
b"used" => {
*self = QuotaComparator::Used;
},
_ => {
*self = QuotaComparator::_T(key.to_string());
}
);
} else {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
Ok(())
}
}
impl Default for QuotaFilter {
fn default() -> Self {
QuotaFilter::_T("".to_string())
}
}
impl Default for QuotaComparator {
fn default() -> Self {
QuotaComparator::_T("".to_string())
}
}
impl From<Id> for QuotaValue {
fn from(id: Id) -> Self {
QuotaValue::Id(id)
}
}
impl JmapObjectId for QuotaValue {
fn as_id(&self) -> Option<Id> {
if let QuotaValue::Id(id) = self {
Some(*id)
} else {
None
}
}
fn as_any_id(&self) -> Option<AnyId> {
self.as_id().map(AnyId::Id)
}
fn as_id_ref(&self) -> Option<&str> {
None
}
fn try_set_id(&mut self, new_id: AnyId) -> bool {
if let AnyId::Id(id) = new_id {
*self = QuotaValue::Id(id);
true
} else {
false
}
}
}
impl JmapObjectId for QuotaProperty {
fn as_id(&self) -> Option<Id> {
None
}
fn as_any_id(&self) -> Option<AnyId> {
None
}
fn as_id_ref(&self) -> Option<&str> {
None
}
fn try_set_id(&mut self, _: AnyId) -> bool {
false
}
}
+200
View File
@@ -0,0 +1,200 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
object::{AnyId, JmapObject, JmapObjectId},
request::deserialize::DeserializeArguments,
};
use registry::{jmap::RegistryValue, schema::prelude::Property, types::EnumImpl};
use std::borrow::Cow;
use types::id::Id;
#[derive(Debug)]
pub struct Registry;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RegistryFilter {
Property {
property: Property,
operator: RegistryFilterOperator,
value: serde_json::Value,
},
_T(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RegistryFilterOperator {
Equal,
GreaterThan,
GreaterThanOrEqual,
LessThan,
LessThanOrEqual,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RegistryComparator {
Property(Property),
_T(String),
}
impl JmapObject for Registry {
type Property = Property;
type Element = RegistryValue;
type Id = Id;
type Filter = RegistryFilter;
type Comparator = RegistryComparator;
type GetArguments = ();
type SetArguments<'de> = ();
type QueryArguments = ();
type CopyArguments = ();
type ParseArguments = ();
const ID_PROPERTY: Self::Property = Property::Id;
}
impl JmapObjectId for Property {
fn as_id(&self) -> Option<Id> {
None
}
fn as_any_id(&self) -> Option<super::AnyId> {
None
}
fn as_id_ref(&self) -> Option<&str> {
None
}
fn try_set_id(&mut self, _: super::AnyId) -> bool {
false
}
}
impl JmapObjectId for RegistryValue {
fn as_id(&self) -> Option<Id> {
if let RegistryValue::Id(id) = self {
Some(*id)
} else {
None
}
}
fn as_any_id(&self) -> Option<AnyId> {
match self {
RegistryValue::Id(id) => Some(AnyId::Id(*id)),
RegistryValue::BlobId(id) => Some(AnyId::BlobId(id.clone())),
_ => None,
}
}
fn as_id_ref(&self) -> Option<&str> {
if let RegistryValue::IdReference(r) = self {
Some(r)
} else {
None
}
}
fn try_set_id(&mut self, new_id: AnyId) -> bool {
match new_id {
AnyId::Id(id) => {
*self = RegistryValue::Id(id);
}
AnyId::BlobId(id) => {
*self = RegistryValue::BlobId(id);
}
}
true
}
}
impl<'de> DeserializeArguments<'de> for RegistryFilter {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'de>,
{
if let Some(property) = Property::parse(key) {
let value = map.next_value()?;
*self = RegistryFilter::Property {
property,
operator: RegistryFilterOperator::Equal,
value,
};
return Ok(());
} else if let Some((property, operator)) = key.rsplit_once("Is")
&& let (Some(property), Some(operator)) = (
Property::parse(property),
RegistryFilterOperator::parse(operator),
)
{
let value = map.next_value()?;
*self = RegistryFilter::Property {
property,
operator,
value,
};
return Ok(());
}
*self = RegistryFilter::_T(key.to_string());
let _ = map.next_value::<serde::de::IgnoredAny>()?;
Ok(())
}
}
impl<'de> DeserializeArguments<'de> for RegistryComparator {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'de>,
{
if key == "property" {
let value = map.next_value::<Cow<str>>()?;
if let Some(property) = Property::parse(value.as_ref()) {
*self = RegistryComparator::Property(property);
} else {
*self = RegistryComparator::_T(value.into_owned());
}
} else {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
Ok(())
}
}
impl RegistryFilterOperator {
pub fn parse(value: &str) -> Option<Self> {
hashify::tiny_map!(value.as_bytes(),
b"GreaterThan" => RegistryFilterOperator::GreaterThan,
b"GreaterThanOrEqual" => RegistryFilterOperator::GreaterThanOrEqual,
b"LessThan" => RegistryFilterOperator::LessThan,
b"LessThanOrEqual" => RegistryFilterOperator::LessThanOrEqual,
)
}
}
impl Default for RegistryFilter {
fn default() -> Self {
RegistryFilter::_T("".to_string())
}
}
impl Default for RegistryComparator {
fn default() -> Self {
RegistryComparator::_T("".to_string())
}
}
@@ -0,0 +1,72 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use jmap_tools::{Element, Key, Property};
use std::{borrow::Cow, str::FromStr};
use types::id::Id;
#[derive(Debug, Clone, Default)]
pub struct SearchSnippet;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum SearchSnippetProperty {
EmailId,
Subject,
Preview,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum SearchSnippetValue {
Id(Id),
}
impl Property for SearchSnippetProperty {
fn try_parse(_: Option<&Key<'_, Self>>, value: &str) -> Option<Self> {
SearchSnippetProperty::parse(value)
}
fn to_cow(&self) -> Cow<'static, str> {
match self {
SearchSnippetProperty::Preview => "preview",
SearchSnippetProperty::Subject => "subject",
SearchSnippetProperty::EmailId => "emailId",
}
.into()
}
}
impl Element for SearchSnippetValue {
type Property = SearchSnippetProperty;
fn try_parse<P>(key: &Key<'_, Self::Property>, value: &str) -> Option<Self> {
if let Key::Property(prop) = key {
match prop {
SearchSnippetProperty::EmailId => {
Id::from_str(value).ok().map(SearchSnippetValue::Id)
}
_ => None,
}
} else {
None
}
}
fn to_cow(&self) -> Cow<'static, str> {
match self {
SearchSnippetValue::Id(id) => id.to_string().into(),
}
}
}
impl SearchSnippetProperty {
fn parse(value: &str) -> Option<Self> {
hashify::tiny_map!(value.as_bytes(),
b"emailId" => SearchSnippetProperty::EmailId,
b"subject" => SearchSnippetProperty::Subject,
b"preview" => SearchSnippetProperty::Preview,
)
}
}
@@ -0,0 +1,308 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
object::{AnyId, JmapObject, JmapObjectId},
request::deserialize::DeserializeArguments,
types::date::UTCDate,
};
use jmap_tools::{Element, Key, Property};
use std::{borrow::Cow, fmt::Display, str::FromStr};
use types::{id::Id, type_state::DataType};
#[derive(Debug, Clone, Default)]
pub struct ShareNotification;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ShareNotificationProperty {
Id,
Created,
ChangedBy,
ChangedByName,
ChangedByEmail,
ChangedByPrincipalId,
ObjectType,
ObjectAccountId,
ObjectId,
OldRights,
NewRights,
Name,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ShareNotificationValue {
Id(Id),
Date(UTCDate),
ObjectType(DataType),
}
impl Property for ShareNotificationProperty {
fn try_parse(_: Option<&Key<'_, Self>>, value: &str) -> Option<Self> {
ShareNotificationProperty::parse(value)
}
fn to_cow(&self) -> Cow<'static, str> {
match self {
ShareNotificationProperty::Id => "id",
ShareNotificationProperty::Created => "created",
ShareNotificationProperty::ChangedBy => "changedBy",
ShareNotificationProperty::ChangedByName => "name",
ShareNotificationProperty::ChangedByEmail => "email",
ShareNotificationProperty::ChangedByPrincipalId => "principalId",
ShareNotificationProperty::ObjectType => "objectType",
ShareNotificationProperty::ObjectAccountId => "objectAccountId",
ShareNotificationProperty::ObjectId => "objectId",
ShareNotificationProperty::OldRights => "oldRights",
ShareNotificationProperty::NewRights => "newRights",
ShareNotificationProperty::Name => "name",
}
.into()
}
}
impl Element for ShareNotificationValue {
type Property = ShareNotificationProperty;
fn try_parse<P>(key: &Key<'_, Self::Property>, value: &str) -> Option<Self> {
if let Key::Property(prop) = key {
match prop {
ShareNotificationProperty::Id
| ShareNotificationProperty::ChangedByPrincipalId
| ShareNotificationProperty::ObjectAccountId
| ShareNotificationProperty::ObjectId => {
Id::from_str(value).ok().map(ShareNotificationValue::Id)
}
ShareNotificationProperty::Created => UTCDate::from_str(value)
.ok()
.map(ShareNotificationValue::Date),
_ => None,
}
} else {
None
}
}
fn to_cow(&self) -> Cow<'static, str> {
match self {
ShareNotificationValue::Id(id) => id.to_string().into(),
ShareNotificationValue::Date(date) => date.to_string().into(),
ShareNotificationValue::ObjectType(ty) => ty.as_str().into(),
}
}
}
impl ShareNotificationProperty {
fn parse(value: &str) -> Option<Self> {
hashify::tiny_map!(value.as_bytes(),
b"id" => ShareNotificationProperty::Id,
b"created" => ShareNotificationProperty::Created,
b"changedBy" => ShareNotificationProperty::ChangedBy,
b"name" => ShareNotificationProperty::ChangedByName,
b"email" => ShareNotificationProperty::ChangedByEmail,
b"principalId" => ShareNotificationProperty::ChangedByPrincipalId,
b"objectType" => ShareNotificationProperty::ObjectType,
b"objectAccountId" => ShareNotificationProperty::ObjectAccountId,
b"objectId" => ShareNotificationProperty::ObjectId,
b"oldRights" => ShareNotificationProperty::OldRights,
b"newRights" => ShareNotificationProperty::NewRights
)
}
}
impl FromStr for ShareNotificationProperty {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
ShareNotificationProperty::parse(s).ok_or(())
}
}
impl JmapObject for ShareNotification {
type Property = ShareNotificationProperty;
type Element = ShareNotificationValue;
type Id = Id;
type Filter = ShareNotificationFilter;
type Comparator = ShareNotificationComparator;
type GetArguments = ();
type SetArguments<'de> = ();
type QueryArguments = ();
type CopyArguments = ();
type ParseArguments = ();
const ID_PROPERTY: Self::Property = ShareNotificationProperty::Id;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ShareNotificationFilter {
After(UTCDate),
Before(UTCDate),
ObjectType(DataType),
ObjectAccountId(Id),
_T(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ShareNotificationComparator {
Created,
_T(String),
}
impl<'de> DeserializeArguments<'de> for ShareNotificationFilter {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'de>,
{
hashify::fnc_map!(key.as_bytes(),
b"after" => {
*self = ShareNotificationFilter::After(map.next_value()?);
},
b"before" => {
*self = ShareNotificationFilter::Before(map.next_value()?);
},
b"objectType" => {
*self = ShareNotificationFilter::ObjectType(map.next_value()?);
},
b"objectAccountId" => {
*self = ShareNotificationFilter::ObjectAccountId(map.next_value()?);
},
_ => {
*self = ShareNotificationFilter::_T(key.to_string());
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
);
Ok(())
}
}
impl<'de> DeserializeArguments<'de> for ShareNotificationComparator {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'de>,
{
if key == "property" {
let value = map.next_value::<Cow<str>>()?;
hashify::fnc_map!(value.as_bytes(),
b"created" => {
*self = ShareNotificationComparator::Created;
},
_ => {
*self = ShareNotificationComparator::_T(value.to_string());
}
);
} else {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
Ok(())
}
}
impl ShareNotificationFilter {
pub fn into_string(self) -> Cow<'static, str> {
match self {
ShareNotificationFilter::After(_) => "after",
ShareNotificationFilter::Before(_) => "before",
ShareNotificationFilter::ObjectType(_) => "objectType",
ShareNotificationFilter::ObjectAccountId(_) => "objectAccountId",
ShareNotificationFilter::_T(s) => return Cow::Owned(s),
}
.into()
}
}
impl ShareNotificationComparator {
pub fn into_string(self) -> Cow<'static, str> {
match self {
ShareNotificationComparator::Created => "created",
ShareNotificationComparator::_T(s) => return Cow::Owned(s),
}
.into()
}
}
impl Default for ShareNotificationFilter {
fn default() -> Self {
ShareNotificationFilter::_T(String::new())
}
}
impl Default for ShareNotificationComparator {
fn default() -> Self {
ShareNotificationComparator::_T(String::new())
}
}
impl TryFrom<ShareNotificationProperty> for Id {
type Error = ();
fn try_from(_: ShareNotificationProperty) -> Result<Self, Self::Error> {
Err(())
}
}
impl From<Id> for ShareNotificationValue {
fn from(id: Id) -> Self {
ShareNotificationValue::Id(id)
}
}
impl JmapObjectId for ShareNotificationValue {
fn as_id(&self) -> Option<Id> {
if let ShareNotificationValue::Id(id) = self {
Some(*id)
} else {
None
}
}
fn as_any_id(&self) -> Option<AnyId> {
if let ShareNotificationValue::Id(id) = self {
Some(AnyId::Id(*id))
} else {
None
}
}
fn as_id_ref(&self) -> Option<&str> {
None
}
fn try_set_id(&mut self, _: AnyId) -> bool {
false
}
}
impl JmapObjectId for ShareNotificationProperty {
fn as_id(&self) -> Option<Id> {
None
}
fn as_any_id(&self) -> Option<AnyId> {
None
}
fn as_id_ref(&self) -> Option<&str> {
None
}
fn try_set_id(&mut self, _: AnyId) -> bool {
false
}
}
impl Display for ShareNotificationProperty {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_cow())
}
}
+284
View File
@@ -0,0 +1,284 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
object::{AnyId, DeserializeArguments, JmapObject, JmapObjectId, MaybeReference, parse_ref},
request::reference::MaybeIdReference,
};
use jmap_tools::{Element, Key, Property};
use std::{borrow::Cow, str::FromStr};
use types::{blob::BlobId, id::Id};
#[derive(Debug, Clone, Default)]
pub struct Sieve;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum SieveProperty {
Id,
Name,
BlobId,
IsActive,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum SieveValue {
Id(Id),
BlobId(BlobId),
IdReference(String),
}
impl Property for SieveProperty {
fn try_parse(_: Option<&Key<'_, Self>>, value: &str) -> Option<Self> {
SieveProperty::parse(value)
}
fn to_cow(&self) -> Cow<'static, str> {
match self {
SieveProperty::BlobId => "blobId",
SieveProperty::Id => "id",
SieveProperty::Name => "name",
SieveProperty::IsActive => "isActive",
}
.into()
}
}
impl Element for SieveValue {
type Property = SieveProperty;
fn try_parse<P>(key: &Key<'_, Self::Property>, value: &str) -> Option<Self> {
if let Key::Property(prop) = key {
match prop {
SieveProperty::Id => match parse_ref(value) {
MaybeReference::Value(v) => Some(SieveValue::Id(v)),
MaybeReference::Reference(v) => Some(SieveValue::IdReference(v)),
MaybeReference::ParseError => None,
},
SieveProperty::BlobId => match parse_ref(value) {
MaybeReference::Value(v) => Some(SieveValue::BlobId(v)),
MaybeReference::Reference(v) => Some(SieveValue::IdReference(v)),
MaybeReference::ParseError => None,
},
_ => None,
}
} else {
None
}
}
fn to_cow(&self) -> Cow<'static, str> {
match self {
SieveValue::Id(id) => id.to_string().into(),
SieveValue::BlobId(blob_id) => blob_id.to_string().into(),
SieveValue::IdReference(r) => format!("#{r}").into(),
}
}
}
impl SieveProperty {
fn parse(value: &str) -> Option<Self> {
hashify::tiny_map!(value.as_bytes(),
b"id" => SieveProperty::Id,
b"name" => SieveProperty::Name,
b"blobId" => SieveProperty::BlobId,
b"isActive" => SieveProperty::IsActive,
)
}
}
#[derive(Debug, Clone, Default)]
pub struct SieveSetArguments {
pub on_success_activate_script: Option<MaybeIdReference<Id>>,
pub on_success_deactivate_script: Option<bool>,
}
impl<'de> DeserializeArguments<'de> for SieveSetArguments {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'de>,
{
hashify::fnc_map!(key.as_bytes(),
b"onSuccessActivateScript" => {
self.on_success_activate_script = map.next_value()?;
},
b"onSuccessDeactivateScript" => {
self.on_success_deactivate_script = map.next_value()?;
},
_ => {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
);
Ok(())
}
}
impl FromStr for SieveProperty {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
SieveProperty::parse(s).ok_or(())
}
}
impl JmapObject for Sieve {
type Property = SieveProperty;
type Element = SieveValue;
type Id = Id;
type Filter = SieveFilter;
type Comparator = SieveComparator;
type GetArguments = ();
type SetArguments<'de> = SieveSetArguments;
type QueryArguments = ();
type CopyArguments = ();
type ParseArguments = ();
const ID_PROPERTY: Self::Property = SieveProperty::Id;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SieveFilter {
Name(String),
IsActive(bool),
_T(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SieveComparator {
Name,
IsActive,
_T(String),
}
impl<'de> DeserializeArguments<'de> for SieveFilter {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'de>,
{
hashify::fnc_map!(key.as_bytes(),
b"name" => {
*self = SieveFilter::Name(map.next_value()?);
},
b"isActive" => {
*self = SieveFilter::IsActive(map.next_value()?);
},
_ => {
*self = SieveFilter::_T(key.to_string());
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
);
Ok(())
}
}
impl<'de> DeserializeArguments<'de> for SieveComparator {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: serde::de::MapAccess<'de>,
{
if key == "property" {
let value = map.next_value::<Cow<str>>()?;
hashify::fnc_map!(value.as_bytes(),
b"name" => {
*self = SieveComparator::Name;
},
b"isActive" => {
*self = SieveComparator::IsActive;
},
_ => {
*self = SieveComparator::_T(key.to_string());
}
);
} else {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
Ok(())
}
}
impl Default for SieveFilter {
fn default() -> Self {
SieveFilter::_T("".to_string())
}
}
impl Default for SieveComparator {
fn default() -> Self {
SieveComparator::_T("".to_string())
}
}
impl From<Id> for SieveValue {
fn from(id: Id) -> Self {
SieveValue::Id(id)
}
}
impl JmapObjectId for SieveValue {
fn as_id(&self) -> Option<Id> {
match self {
SieveValue::Id(id) => Some(*id),
_ => None,
}
}
fn as_any_id(&self) -> Option<AnyId> {
match self {
SieveValue::Id(id) => Some(AnyId::Id(*id)),
SieveValue::BlobId(id) => Some(AnyId::BlobId(id.clone())),
SieveValue::IdReference(_) => None,
}
}
fn as_id_ref(&self) -> Option<&str> {
if let SieveValue::IdReference(r) = self {
Some(r)
} else {
None
}
}
fn try_set_id(&mut self, new_id: AnyId) -> bool {
match new_id {
AnyId::Id(id) => {
*self = SieveValue::Id(id);
}
AnyId::BlobId(id) => {
*self = SieveValue::BlobId(id);
}
}
true
}
}
impl JmapObjectId for SieveProperty {
fn as_id(&self) -> Option<Id> {
None
}
fn as_any_id(&self) -> Option<AnyId> {
None
}
fn as_id_ref(&self) -> Option<&str> {
None
}
fn try_set_id(&mut self, _: AnyId) -> bool {
false
}
}
+147
View File
@@ -0,0 +1,147 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use jmap_tools::{Element, Key, Property};
use std::{borrow::Cow, str::FromStr};
use types::id::Id;
use crate::object::{AnyId, JmapObject, JmapObjectId};
#[derive(Debug, Clone, Default)]
pub struct Thread;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ThreadProperty {
Id,
EmailIds,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ThreadValue {
Id(Id),
}
impl Property for ThreadProperty {
fn try_parse(_: Option<&Key<'_, Self>>, value: &str) -> Option<Self> {
ThreadProperty::parse(value)
}
fn to_cow(&self) -> Cow<'static, str> {
match self {
ThreadProperty::Id => "id",
ThreadProperty::EmailIds => "emailIds",
}
.into()
}
}
impl Element for ThreadValue {
type Property = ThreadProperty;
fn try_parse<P>(key: &Key<'_, Self::Property>, value: &str) -> Option<Self> {
if let Key::Property(_) = key {
Id::from_str(value).ok().map(ThreadValue::Id)
} else {
None
}
}
fn to_cow(&self) -> Cow<'static, str> {
match self {
ThreadValue::Id(id) => id.to_string().into(),
}
}
}
impl ThreadProperty {
fn parse(value: &str) -> Option<Self> {
hashify::tiny_map!(value.as_bytes(),
b"id" => ThreadProperty::Id,
b"emailIds" => ThreadProperty::EmailIds,
)
}
}
impl FromStr for ThreadProperty {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
ThreadProperty::parse(s).ok_or(())
}
}
impl JmapObject for Thread {
type Property = ThreadProperty;
type Element = ThreadValue;
type Id = Id;
type Filter = ();
type Comparator = ();
type GetArguments = ();
type SetArguments<'de> = ();
type QueryArguments = ();
type CopyArguments = ();
type ParseArguments = ();
const ID_PROPERTY: Self::Property = ThreadProperty::Id;
}
impl From<Id> for ThreadValue {
fn from(id: Id) -> Self {
ThreadValue::Id(id)
}
}
impl JmapObjectId for ThreadValue {
fn as_id(&self) -> Option<Id> {
match self {
ThreadValue::Id(id) => Some(*id),
}
}
fn as_any_id(&self) -> Option<AnyId> {
self.as_id().map(AnyId::Id)
}
fn as_id_ref(&self) -> Option<&str> {
None
}
fn try_set_id(&mut self, new_id: AnyId) -> bool {
if let AnyId::Id(id) = new_id {
*self = ThreadValue::Id(id);
true
} else {
false
}
}
}
impl JmapObjectId for ThreadProperty {
fn as_id(&self) -> Option<Id> {
None
}
fn as_any_id(&self) -> Option<AnyId> {
None
}
fn as_id_ref(&self) -> Option<&str> {
None
}
fn try_set_id(&mut self, _: AnyId) -> bool {
false
}
}
@@ -0,0 +1,180 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
object::{AnyId, JmapObject, JmapObjectId},
types::date::UTCDate,
};
use jmap_tools::{Element, Key, Property};
use std::{borrow::Cow, str::FromStr};
use types::id::Id;
#[derive(Debug, Clone, Default)]
pub struct VacationResponse;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum VacationResponseProperty {
Id,
IsEnabled,
FromDate,
ToDate,
Subject,
TextBody,
HtmlBody,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum VacationResponseValue {
Id(Id),
Date(UTCDate),
}
impl Property for VacationResponseProperty {
fn try_parse(_: Option<&Key<'_, Self>>, value: &str) -> Option<Self> {
VacationResponseProperty::parse(value)
}
fn to_cow(&self) -> Cow<'static, str> {
match self {
VacationResponseProperty::HtmlBody => "htmlBody",
VacationResponseProperty::Id => "id",
VacationResponseProperty::TextBody => "textBody",
VacationResponseProperty::FromDate => "fromDate",
VacationResponseProperty::IsEnabled => "isEnabled",
VacationResponseProperty::ToDate => "toDate",
VacationResponseProperty::Subject => "subject",
}
.into()
}
}
impl Element for VacationResponseValue {
type Property = VacationResponseProperty;
fn try_parse<P>(key: &Key<'_, Self::Property>, value: &str) -> Option<Self> {
if let Key::Property(prop) = key {
match prop {
VacationResponseProperty::Id => {
Id::from_str(value).ok().map(VacationResponseValue::Id)
}
VacationResponseProperty::FromDate | VacationResponseProperty::ToDate => {
UTCDate::from_str(value)
.ok()
.map(VacationResponseValue::Date)
}
_ => None,
}
} else {
None
}
}
fn to_cow(&self) -> Cow<'static, str> {
match self {
VacationResponseValue::Id(id) => id.to_string().into(),
VacationResponseValue::Date(utcdate) => utcdate.to_string().into(),
}
}
}
impl VacationResponseProperty {
fn parse(value: &str) -> Option<Self> {
hashify::tiny_map!(value.as_bytes(),
b"id" => VacationResponseProperty::Id,
b"isEnabled" => VacationResponseProperty::IsEnabled,
b"fromDate" => VacationResponseProperty::FromDate,
b"toDate" => VacationResponseProperty::ToDate,
b"textBody" => VacationResponseProperty::TextBody,
b"htmlBody" => VacationResponseProperty::HtmlBody,
b"subject" => VacationResponseProperty::Subject,
)
}
}
impl FromStr for VacationResponseProperty {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
VacationResponseProperty::parse(s).ok_or(())
}
}
impl JmapObject for VacationResponse {
type Property = VacationResponseProperty;
type Element = VacationResponseValue;
type Id = Id;
type Filter = ();
type Comparator = ();
type GetArguments = ();
type SetArguments<'de> = ();
type QueryArguments = ();
type CopyArguments = ();
type ParseArguments = ();
const ID_PROPERTY: Self::Property = VacationResponseProperty::Id;
}
impl From<Id> for VacationResponseValue {
fn from(id: Id) -> Self {
VacationResponseValue::Id(id)
}
}
impl JmapObjectId for VacationResponseValue {
fn as_id(&self) -> Option<Id> {
match self {
VacationResponseValue::Id(id) => Some(*id),
_ => None,
}
}
fn as_any_id(&self) -> Option<AnyId> {
match self {
VacationResponseValue::Id(id) => Some(AnyId::Id(*id)),
_ => None,
}
}
fn as_id_ref(&self) -> Option<&str> {
None
}
fn try_set_id(&mut self, new_id: AnyId) -> bool {
if let AnyId::Id(id) = new_id {
*self = VacationResponseValue::Id(id);
true
} else {
false
}
}
}
impl JmapObjectId for VacationResponseProperty {
fn as_id(&self) -> Option<Id> {
None
}
fn as_any_id(&self) -> Option<AnyId> {
None
}
fn as_id_ref(&self) -> Option<&str> {
None
}
fn try_set_id(&mut self, _: AnyId) -> bool {
false
}
}
+280
View File
@@ -0,0 +1,280 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
object::{AnyId, JmapObjectId},
references::{
Graph,
jsptr::{EvalResults, ResponsePtr},
},
request::reference::ResultReference,
response::{ChangesResponseMethod, GetResponseMethod, Response, ResponseMethod},
};
use compact_str::format_compact;
use jmap_tools::{Element, Key, Property, Value};
use types::{blob::BlobId, id::Id};
impl Response<'_> {
pub(crate) fn eval_result_references(&self, rr: &ResultReference) -> trc::Result<EvalResults> {
let mut results = EvalResults::default();
for response in &self.method_responses {
if response.id == rr.result_of && response.name == rr.name {
let path = rr.path.iter();
let success = match &response.method {
ResponseMethod::Get(response) => match response {
GetResponseMethod::Email(response) => {
response.eval_jptr(path, &mut results)
}
GetResponseMethod::Mailbox(response) => {
response.eval_jptr(path, &mut results)
}
GetResponseMethod::Thread(response) => {
response.eval_jptr(path, &mut results)
}
GetResponseMethod::Identity(response) => {
response.eval_jptr(path, &mut results)
}
GetResponseMethod::EmailSubmission(response) => {
response.eval_jptr(path, &mut results)
}
GetResponseMethod::PushSubscription(response) => {
response.eval_jptr(path, &mut results)
}
GetResponseMethod::Sieve(response) => {
response.eval_jptr(path, &mut results)
}
GetResponseMethod::VacationResponse(response) => {
response.eval_jptr(path, &mut results)
}
GetResponseMethod::Principal(response) => {
response.eval_jptr(path, &mut results)
}
GetResponseMethod::Quota(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)
}
GetResponseMethod::FileNode(response) => {
response.eval_jptr(path, &mut results)
}
GetResponseMethod::Calendar(response) => {
response.eval_jptr(path, &mut results)
}
GetResponseMethod::CalendarEvent(response) => {
response.eval_jptr(path, &mut results)
}
GetResponseMethod::CalendarEventNotification(response) => {
response.eval_jptr(path, &mut results)
}
GetResponseMethod::ParticipantIdentity(response) => {
response.eval_jptr(path, &mut results)
}
GetResponseMethod::ShareNotification(response) => {
response.eval_jptr(path, &mut results)
}
GetResponseMethod::PrincipalAvailability(response) => {
response.eval_jptr(path, &mut results)
}
GetResponseMethod::Registry(response) => {
response.eval_jptr(path, &mut results)
}
},
ResponseMethod::Changes(response) => match response {
ChangesResponseMethod::Email(response) => {
response.eval_jptr(path, &mut results)
}
ChangesResponseMethod::Mailbox(response) => {
response.eval_jptr(path, &mut results)
}
ChangesResponseMethod::Thread(response) => {
response.eval_jptr(path, &mut results)
}
ChangesResponseMethod::Identity(response) => {
response.eval_jptr(path, &mut results)
}
ChangesResponseMethod::EmailSubmission(response) => {
response.eval_jptr(path, &mut results)
}
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)
}
ChangesResponseMethod::FileNode(response) => {
response.eval_jptr(path, &mut results)
}
ChangesResponseMethod::Calendar(response) => {
response.eval_jptr(path, &mut results)
}
ChangesResponseMethod::CalendarEvent(response) => {
response.eval_jptr(path, &mut results)
}
ChangesResponseMethod::CalendarEventNotification(response) => {
response.eval_jptr(path, &mut results)
}
ChangesResponseMethod::ShareNotification(response) => {
response.eval_jptr(path, &mut results)
}
},
ResponseMethod::Query(response) => response.eval_jptr(path, &mut results),
ResponseMethod::QueryChanges(response) => {
response.eval_jptr(path, &mut results)
}
_ => false,
};
if success {
return Ok(results);
}
}
}
Err(trc::JmapEvent::InvalidResultReference
.into_err()
.details(format_compact!(
"Result reference to {}#{} not found.",
rr.result_of,
rr.name
)))
}
pub(crate) fn eval_id_reference(&self, ir: &str) -> trc::Result<Id> {
if let Some(AnyId::Id(id)) = self.created_ids.get(ir) {
Ok(*id)
} else {
Err(trc::JmapEvent::InvalidResultReference
.into_err()
.details(format_compact!("Id reference {ir:?} not found.")))
}
}
pub(crate) fn eval_blob_id_reference(&self, ir: &str) -> trc::Result<BlobId> {
if let Some(AnyId::BlobId(id)) = self.created_ids.get(ir) {
Ok(id.clone())
} else {
Err(trc::JmapEvent::InvalidResultReference
.into_err()
.details(format_compact!("blobId reference {ir:?} not found.")))
}
}
}
pub(crate) trait EvalObjectReferences {
fn eval_object_references(
&mut self,
response: &Response<'_>,
graph: &mut Graph<'_>,
depth: usize,
max_depth: usize,
eval_strings: bool,
) -> trc::Result<()>;
}
impl<'x, P, E> EvalObjectReferences for Value<'x, P, E>
where
P: Property + JmapObjectId,
E: Element<Property = P> + JmapObjectId,
{
fn eval_object_references(
&mut self,
response: &Response<'_>,
graph: &mut Graph<'_>,
depth: usize,
max_depth: usize,
eval_strings: bool,
) -> trc::Result<()> {
match self {
Value::Element(element) => {
if let Some(id_ref) = element.as_id_ref() {
if let Some(id) = response.created_ids.get(id_ref) {
if !element.try_set_id(id.clone()) {
return Err(trc::JmapEvent::InvalidResultReference
.into_err()
.details("Id reference points to invalid type."));
}
} else if let Graph::Some { child_id, graph } = graph {
graph
.entry(child_id.to_string())
.or_insert_with(Vec::new)
.push(id_ref.to_string());
} else {
return Err(trc::JmapEvent::InvalidResultReference
.into_err()
.details(format_compact!("Id reference {id_ref:?} not found.")));
}
}
}
Value::Array(items) if depth < max_depth => {
// Resolve references in arrays (e.g. emailIds: [#idRef1, #idRef2])
for item in items {
item.eval_object_references(
response,
graph,
depth + 1,
max_depth,
eval_strings,
)?;
}
}
Value::Object(items) if depth < max_depth => {
// Resolve references in JMAP sets (e.g. mailboxIds: { "#idRef1": true, "#idRef2": true })
for (key, value) in items.as_mut_vec() {
if let Key::Property(property) = key
&& let Some(id_ref) = property.as_id_ref()
{
if let Some(id) = response.created_ids.get(id_ref) {
if !property.try_set_id(id.clone()) {
return Err(trc::JmapEvent::InvalidResultReference
.into_err()
.details("Id reference points to invalid type."));
}
} else {
return Err(trc::JmapEvent::InvalidResultReference
.into_err()
.details(format_compact!("Id reference {id_ref:?} not found.")));
}
} else if eval_strings
&& let Some(id) = key
.as_string_key()
.and_then(|k| k.strip_prefix('#'))
.and_then(|id_ref| response.created_ids.get(id_ref))
{
*key = Key::Owned(match id {
AnyId::Id(id) => id.to_string(),
AnyId::BlobId(id) => id.to_string(),
});
}
if matches!(
value,
Value::Element(_) | Value::Array(_) | Value::Object(_)
) {
value.eval_object_references(
response,
graph,
depth + 1,
max_depth,
eval_strings,
)?;
}
}
}
_ => {}
}
Ok(())
}
}
+335
View File
@@ -0,0 +1,335 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
method::{
PropertyWrapper,
availability::{BusyPeriod, GetAvailabilityResponse},
changes::ChangesResponse,
get::GetResponse,
query::QueryResponse,
query_changes::{AddedItem, QueryChangesResponse},
},
object::{
AnyId, JmapObject, JmapObjectId,
calendar_event_notification::{
CalendarEventNotificationGetResponse, CalendarEventNotificationObject,
},
},
request::reference::ResultReference,
};
use compact_str::format_compact;
use jmap_tools::{Element, JsonPointerItem, JsonPointerIter, Key, Null, Property, Value};
use std::{borrow::Cow, str::FromStr};
use types::{blob::BlobId, id::Id};
pub(crate) trait ResponsePtr {
fn eval_jptr(&self, pointer: JsonPointerIter<'_, Null>, results: &mut EvalResults) -> bool;
}
#[derive(Debug, Default)]
#[repr(transparent)]
pub(crate) struct EvalResults(Vec<EvalResult>);
#[derive(Debug)]
pub(crate) enum EvalResult {
Id(AnyId),
Property(Cow<'static, str>),
}
impl<T> ResponsePtr for Vec<T>
where
T: ResponsePtr,
{
fn eval_jptr(&self, mut pointer: JsonPointerIter<'_, Null>, results: &mut EvalResults) -> bool {
match pointer.next() {
Some(JsonPointerItem::Number(n)) => {
if let Some(v) = self.get(*n as usize) {
v.eval_jptr(pointer, results);
}
}
Some(JsonPointerItem::Wildcard | JsonPointerItem::Root) | None => {
for v in self {
v.eval_jptr(pointer.clone(), results);
}
}
_ => (),
}
true
}
}
impl<'ctx, P, E> ResponsePtr for Value<'ctx, P, E>
where
P: Property,
E: Element<Property = P> + JmapObjectId,
{
fn eval_jptr(&self, mut pointer: JsonPointerIter<'_, Null>, results: &mut EvalResults) -> bool {
match pointer.next() {
Some(JsonPointerItem::Key(key)) => {
if let Some(key) = key.as_string_key()
&& let Value::Object(map) = self
&& let Some(v) = map.get(&Key::Borrowed(key))
{
v.eval_jptr(pointer, results);
}
}
Some(JsonPointerItem::Number(n)) => match self {
Value::Array(values) => {
if let Some(v) = values.get(*n as usize) {
v.eval_jptr(pointer, results);
}
}
Value::Object(map) => {
let n = Key::Owned(n.to_string());
if let Some(v) = map.get(&n) {
v.eval_jptr(pointer, results);
}
}
_ => {}
},
Some(JsonPointerItem::Wildcard) => match self {
Value::Array(values) => {
for v in values {
v.eval_jptr(pointer.clone(), results);
}
}
Value::Object(map) => {
for v in map.values() {
v.eval_jptr(pointer.clone(), results);
}
}
_ => {}
},
Some(JsonPointerItem::Root) | None => match self {
Value::Element(e) => {
if let Some(id) = e.as_any_id() {
results.0.push(EvalResult::Id(id));
}
}
Value::Array(list) => {
for item in list {
if let Value::Element(e) = item
&& let Some(id) = e.as_any_id()
{
results.0.push(EvalResult::Id(id));
}
}
}
_ => (),
},
}
true
}
}
impl ResponsePtr for Id {
fn eval_jptr(&self, _pointer: JsonPointerIter<'_, Null>, results: &mut EvalResults) -> bool {
results.0.push(EvalResult::Id(AnyId::Id(*self)));
true
}
}
impl ResponsePtr for BlobId {
fn eval_jptr(&self, _pointer: JsonPointerIter<'_, Null>, results: &mut EvalResults) -> bool {
results.0.push(EvalResult::Id(AnyId::BlobId(self.clone())));
true
}
}
impl<T: Property> ResponsePtr for PropertyWrapper<T> {
fn eval_jptr(&self, _: JsonPointerIter<'_, Null>, results: &mut EvalResults) -> bool {
results.0.push(EvalResult::Property(self.0.to_cow()));
true
}
}
impl<T: JmapObject> ResponsePtr for GetResponse<T> {
fn eval_jptr(&self, mut pointer: JsonPointerIter<'_, Null>, results: &mut EvalResults) -> bool {
match pointer.next().and_then(|item| item.as_string_key()) {
Some("list") => {
self.list.eval_jptr(pointer, results);
true
}
_ => false,
}
}
}
impl<T: JmapObject> ResponsePtr for ChangesResponse<T> {
fn eval_jptr(&self, mut pointer: JsonPointerIter<'_, Null>, results: &mut EvalResults) -> bool {
if let Some(property) = pointer.next().and_then(|item| item.as_string_key()) {
hashify::fnc_map!(property.as_bytes(),
"created" => {
self.created.eval_jptr(pointer, results);
},
"updated" => {
self.updated.eval_jptr(pointer, results);
},
"updatedProperties" => {
if let Some(props) = &self.updated_properties {
props.eval_jptr(pointer, results);
}
},
_ => {
return false;
}
);
true
} else {
false
}
}
}
impl ResponsePtr for QueryResponse {
fn eval_jptr(&self, mut pointer: JsonPointerIter<'_, Null>, results: &mut EvalResults) -> bool {
match pointer.next().and_then(|item| item.as_string_key()) {
Some("ids") => {
self.ids.eval_jptr(pointer, results);
true
}
_ => false,
}
}
}
impl ResponsePtr for QueryChangesResponse {
fn eval_jptr(&self, mut pointer: JsonPointerIter<'_, Null>, results: &mut EvalResults) -> bool {
match pointer.next().and_then(|item| item.as_string_key()) {
Some("added") => {
self.added.eval_jptr(pointer, results);
true
}
_ => false,
}
}
}
impl ResponsePtr for AddedItem {
fn eval_jptr(&self, mut pointer: JsonPointerIter<'_, Null>, results: &mut EvalResults) -> bool {
match pointer.next().and_then(|item| item.as_string_key()) {
Some("id") => {
results.0.push(EvalResult::Id(AnyId::Id(self.id)));
true
}
_ => false,
}
}
}
impl ResponsePtr for CalendarEventNotificationGetResponse {
fn eval_jptr(&self, mut pointer: JsonPointerIter<'_, Null>, results: &mut EvalResults) -> bool {
match pointer.next().and_then(|item| item.as_string_key()) {
Some("list") => {
self.list.eval_jptr(pointer, results);
true
}
_ => false,
}
}
}
impl ResponsePtr for CalendarEventNotificationObject {
fn eval_jptr(&self, mut pointer: JsonPointerIter<'_, Null>, results: &mut EvalResults) -> bool {
match pointer.next().and_then(|item| item.as_string_key()) {
Some("id") => {
results.0.push(EvalResult::Id(AnyId::Id(self.id)));
true
}
Some("calendarEventId") => {
if let Some(id) = &self.calendar_event_id {
results.0.push(EvalResult::Id(AnyId::Id(*id)));
}
true
}
Some("event") => {
if let Some(event) = &self.event {
event.0.eval_jptr(pointer, results);
}
true
}
_ => false,
}
}
}
impl ResponsePtr for GetAvailabilityResponse {
fn eval_jptr(&self, mut pointer: JsonPointerIter<'_, Null>, results: &mut EvalResults) -> bool {
match pointer.next().and_then(|item| item.as_string_key()) {
Some("list") => {
self.list.eval_jptr(pointer, results);
true
}
_ => false,
}
}
}
impl ResponsePtr for BusyPeriod {
fn eval_jptr(&self, mut pointer: JsonPointerIter<'_, Null>, results: &mut EvalResults) -> bool {
match pointer.next().and_then(|item| item.as_string_key()) {
Some("event") => {
if let Some(event) = &self.event {
event.0.eval_jptr(pointer, results);
}
true
}
_ => false,
}
}
}
impl EvalResults {
pub fn into_ids<T: TryFrom<AnyId>>(
self,
rr: &ResultReference,
) -> impl Iterator<Item = trc::Result<T>> {
self.0.into_iter().map(move |id| {
if let EvalResult::Id(any_id) = id {
T::try_from(any_id).map_err(|_| {
trc::JmapEvent::InvalidResultReference
.into_err()
.details(format_compact!(
"Failed to evaluate {rr} result reference: Invalid Id type."
))
})
} else {
Err(trc::JmapEvent::InvalidResultReference
.into_err()
.details(format_compact!(
"Failed to evaluate {rr} result reference: Invalid Id type."
)))
}
})
}
pub fn into_properties<T: Property + FromStr>(
self,
rr: &ResultReference,
) -> impl Iterator<Item = trc::Result<T>> {
self.0.into_iter().map(move |prop| {
if let EvalResult::Property(prop) = prop {
T::from_str(&prop).map_err(|_| {
trc::JmapEvent::InvalidResultReference
.into_err()
.details(format_compact!(
"Failed to evaluate {rr} result reference: Invalid property."
))
})
} else {
Err(trc::JmapEvent::InvalidResultReference
.into_err()
.details(format_compact!(
"Failed to evaluate {rr} result reference: Invalid property."
)))
}
})
}
}
+944
View File
@@ -0,0 +1,944 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use compact_str::format_compact;
use std::collections::HashMap;
use utils::map::vec_map::VecMap;
pub mod eval;
pub mod jsptr;
pub mod resolve;
pub(crate) enum Graph<'x> {
Some {
child_id: &'x str,
graph: &'x mut HashMap<String, Vec<String>>,
},
None,
}
fn topological_sort<T>(
create: &mut VecMap<String, T>,
graph: HashMap<String, Vec<String>>,
) -> trc::Result<VecMap<String, T>> {
// Make sure all references exist
for (from_id, to_ids) in graph.iter() {
for to_id in to_ids {
if !create.contains_key(to_id) {
return Err(trc::JmapEvent::InvalidResultReference.into_err().details(
format_compact!(
"Invalid reference to non-existing object {to_id:?} from {from_id:?}"
),
));
}
}
}
let mut sorted_create = VecMap::with_capacity(create.len());
let mut it_stack = Vec::new();
let keys = graph.keys().cloned().collect::<Vec<_>>();
let mut it = keys.iter();
'main: loop {
while let Some(from_id) = it.next() {
if let Some(to_ids) = graph.get(from_id) {
it_stack.push((it, from_id));
if it_stack.len() > 1000 {
return Err(trc::JmapEvent::InvalidArguments
.into_err()
.details("Cyclical references are not allowed."));
}
it = to_ids.iter();
continue;
} else if let Some((id, value)) = create.remove_entry(from_id) {
sorted_create.append(id, value);
if create.is_empty() {
break 'main;
}
}
}
if let Some((prev_it, from_id)) = it_stack.pop() {
it = prev_it;
if let Some((id, value)) = create.remove_entry(from_id) {
sorted_create.append(id, value);
if create.is_empty() {
break 'main;
}
}
} else {
break;
}
}
// Add remaining items
if !create.is_empty() {
for (id, value) in std::mem::take(create) {
sorted_create.append(id, value);
}
}
Ok(sorted_create)
}
#[cfg(test)]
mod tests {
use crate::references::Graph;
use crate::references::eval::EvalObjectReferences;
use crate::{
method::{changes::ChangesResponse, get::GetResponse, query::QueryResponse},
object::{
email::{EmailProperty, EmailValue},
mailbox::{MailboxProperty, MailboxValue},
thread::{ThreadProperty, ThreadValue},
},
request::{
Call, GetRequestMethod, Request, RequestMethod, SetRequestMethod,
reference::{MaybeIdReference, MaybeResultReference},
},
response::{ChangesResponseMethod, GetResponseMethod, Response, ResponseMethod},
};
use jmap_tools::{Key, Map, Value};
use std::collections::HashMap;
use types::id::Id;
#[test]
fn eval_value_references() {
let request = Request::parse(
br##"{
"using":["urn:ietf:params:jmap:mail"],
"methodCalls": [[ "Email/query", {
"accountId": "a",
"filter": { "inMailbox": "a" },
"sort": [{ "property": "receivedAt", "isAscending": false }],
"collapseThreads": true,
"position": 0,
"limit": 10,
"calculateTotal": true
}, "t0" ],
[ "Email/get", {
"accountId": "a",
"#ids": {
"resultOf": "t0",
"name": "Email/query",
"path": "/ids"
},
"properties": [ "threadId" ]
}, "t1" ],
[ "Thread/get", {
"accountId": "a",
"#ids": {
"resultOf": "t1",
"name": "Email/get",
"path": "/list/*/threadId"
}
}, "t2" ],
[ "Email/get", {
"accountId": "a",
"#ids": {
"resultOf": "t2",
"name": "Thread/get",
"path": "/list/*/emailIds"
},
"properties": [ "from", "receivedAt", "subject" ]
}, "t3" ]]
}"##,
100,
1024 * 1024,
)
.unwrap();
let mut response = Response::new(
1234,
request.created_ids.unwrap_or_default(),
request.method_calls.len(),
);
assert_eq!(request.method_calls.len(), 4);
for (test_num, mut call) in request.method_calls.into_iter().enumerate() {
match test_num {
0 => {
response.method_responses.push(Call {
id: call.id,
name: call.name,
method: ResponseMethod::Query(QueryResponse {
account_id: Id::new(1),
query_state: Default::default(),
can_calculate_changes: Default::default(),
position: Default::default(),
ids: vec![Id::new(4), Id::new(5)],
total: Default::default(),
limit: Default::default(),
}),
});
}
1 => {
response.resolve_references(&mut call.method).unwrap();
match call.method {
RequestMethod::Get(GetRequestMethod::Email(req)) => {
assert_eq!(
req.ids,
Some(MaybeResultReference::Value(vec![
MaybeIdReference::Id(Id::new(4)),
MaybeIdReference::Id(Id::new(5))
]))
);
}
_ => panic!("Expected Email Get Request"),
}
response.method_responses.push(Call {
id: call.id,
name: call.name,
method: ResponseMethod::Get(GetResponseMethod::Email(GetResponse {
account_id: Id::new(1).into(),
state: Default::default(),
list: vec![
Value::Object(Map::from(vec![(
Key::Property(EmailProperty::ThreadId),
Value::Element(EmailValue::Id(Id::new(9))),
)])),
Value::Object(Map::from(vec![(
Key::Property(EmailProperty::ThreadId),
Value::Element(EmailValue::Id(Id::new(10))),
)])),
],
not_found: Default::default(),
})),
});
}
2 => {
response.resolve_references(&mut call.method).unwrap();
match call.method {
RequestMethod::Get(GetRequestMethod::Thread(req)) => {
assert_eq!(
req.ids,
Some(MaybeResultReference::Value(vec![
MaybeIdReference::Id(Id::new(9)),
MaybeIdReference::Id(Id::new(10))
]))
);
}
_ => panic!("Expected Thread Get Request"),
}
response.method_responses.push(Call {
id: call.id,
name: call.name,
method: ResponseMethod::Get(GetResponseMethod::Thread(GetResponse {
account_id: Id::new(1).into(),
state: Default::default(),
list: vec![
Value::Object(Map::from(vec![(
Key::Property(ThreadProperty::EmailIds),
Value::Array(vec![
Value::Element(ThreadValue::Id(Id::new(100))),
Value::Element(ThreadValue::Id(Id::new(101))),
]),
)])),
Value::Object(Map::from(vec![(
Key::Property(ThreadProperty::EmailIds),
Value::Array(vec![
Value::Element(ThreadValue::Id(Id::new(102))),
Value::Element(ThreadValue::Id(Id::new(103))),
]),
)])),
],
not_found: Default::default(),
})),
});
}
3 => {
response.resolve_references(&mut call.method).unwrap();
match call.method {
RequestMethod::Get(GetRequestMethod::Email(req)) => {
assert_eq!(
req.ids,
Some(MaybeResultReference::Value(vec![
MaybeIdReference::Id(Id::new(100)),
MaybeIdReference::Id(Id::new(101)),
MaybeIdReference::Id(Id::new(102)),
MaybeIdReference::Id(Id::new(103)),
]))
);
}
_ => panic!("Expected Mailbox Get Request"),
}
}
_ => panic!("Unexpected invocation {}", test_num),
}
}
}
#[test]
fn eval_property_references() {
let request = Request::parse(
br##"{
"using":["urn:ietf:params:jmap:mail"],
"methodCalls": [
["Mailbox/changes",{
"accountId":"s",
"sinceState":"srxqk071myhgkyay"
},"0"],
["Mailbox/get",{
"accountId":"s",
"#ids":{"name":"Mailbox/changes","path":"/created","resultOf":"0"}
},"1"],
["Mailbox/get",{
"accountId":"s",
"#ids":{"name":"Mailbox/changes","path":"/updated","resultOf":"0"},
"#properties":{"name":"Mailbox/changes","path":"/updatedProperties","resultOf":"0"}
},"2"]
]
}"##,
100,
1024 * 1024,
)
.unwrap();
let mut response = Response::new(
1234,
request.created_ids.unwrap_or_default(),
request.method_calls.len(),
);
assert_eq!(request.method_calls.len(), 3);
for (test_num, mut call) in request.method_calls.into_iter().enumerate() {
match test_num {
0 => {
response.method_responses.push(Call {
id: call.id,
name: call.name,
method: ResponseMethod::Changes(ChangesResponseMethod::Mailbox(Box::new(
ChangesResponse {
account_id: Id::new(1),
old_state: Default::default(),
new_state: Default::default(),
has_more_changes: Default::default(),
created: Default::default(),
updated: vec![Id::new(2), Id::new(3)],
destroyed: Default::default(),
updated_properties: Some(vec![
MailboxProperty::Name.into(),
MailboxProperty::ParentId.into(),
]),
},
))),
});
}
1 => {
response.resolve_references(&mut call.method).unwrap();
match call.method {
RequestMethod::Get(GetRequestMethod::Mailbox(req)) => {
assert_eq!(req.ids, Some(MaybeResultReference::Value(vec![])));
}
_ => panic!("Expected Mailbox Get Request"),
}
}
2 => {
response.resolve_references(&mut call.method).unwrap();
match call.method {
RequestMethod::Get(GetRequestMethod::Mailbox(req)) => {
assert_eq!(
req.ids,
Some(MaybeResultReference::Value(vec![
MaybeIdReference::Id(Id::new(2)),
MaybeIdReference::Id(Id::new(3))
]))
);
}
_ => panic!("Expected Mailbox Get Request"),
}
}
_ => panic!("Unexpected invocation {}", test_num),
}
}
}
#[test]
fn eval_create_references() {
let request = Request::parse(
br##"{
"using": [
"urn:ietf:params:jmap:core",
"urn:ietf:params:jmap:mail"
],
"methodCalls": [
[
"Mailbox/set",
{
"accountId": "b",
"create": {
"a": {
"name": "Folder a",
"parentId": "#b"
},
"b": {
"name": "Folder b",
"parentId": "#c"
},
"c": {
"name": "Folder c",
"parentId": "#d"
},
"d": {
"name": "Folder d",
"parentId": "#e"
},
"e": {
"name": "Folder e",
"parentId": "#f"
},
"f": {
"name": "Folder f",
"parentId": "#g"
},
"g": {
"name": "Folder g",
"parentId": null
}
}
},
"fulltree"
],
[
"Mailbox/set",
{
"accountId": "b",
"create": {
"a1": {
"name": "Folder a1",
"parentId": null
},
"b2": {
"name": "Folder b2",
"parentId": "#a1"
},
"c3": {
"name": "Folder c3",
"parentId": "#a1"
},
"d4": {
"name": "Folder d4",
"parentId": "#b2"
},
"e5": {
"name": "Folder e5",
"parentId": "#b2"
},
"f6": {
"name": "Folder f6",
"parentId": "#d4"
},
"g7": {
"name": "Folder g7",
"parentId": "#e5"
}
}
},
"fulltree2"
],
[
"Mailbox/set",
{
"accountId": "b",
"create": {
"z": {
"name": "Folder Z",
"parentId": "#x"
},
"y": {
"name": null
},
"x": {
"name": "Folder X"
}
}
},
"xyz"
],
[
"Mailbox/set",
{
"accountId": "b",
"create": {
"a": {
"name": "Folder a",
"parentId": "#b"
},
"b": {
"name": "Folder b",
"parentId": "#c"
},
"c": {
"name": "Folder c",
"parentId": "#d"
},
"d": {
"name": "Folder d",
"parentId": "#a"
}
}
},
"circular"
]
]
}"##,
100,
1024 * 1024,
)
.unwrap();
let response = Response::new(
1234,
request.created_ids.unwrap_or_default(),
request.method_calls.len(),
);
for (test_num, mut call) in request.method_calls.into_iter().enumerate() {
match response.resolve_references(&mut call.method) {
Ok(_) => assert!(
(0..3).contains(&test_num),
"Unexpected invocation {}",
test_num
),
Err(err) => {
assert_eq!(test_num, 3);
assert!(
err.matches(trc::EventType::Jmap(trc::JmapEvent::InvalidArguments)),
"{:?}",
err
);
continue;
}
}
if let RequestMethod::Set(SetRequestMethod::Mailbox(request)) = call.method {
if test_num == 0 {
assert_eq!(
request
.create
.unwrap()
.into_iter()
.map(|b| b.0)
.collect::<Vec<_>>(),
["g", "f", "e", "d", "c", "b", "a"]
.iter()
.map(|i| i.to_string())
.collect::<Vec<_>>()
);
} else if test_num == 1 {
let mut pending_ids = vec!["a1", "b2", "d4", "e5", "f6", "c3", "g7"];
for (id, _) in request.create.as_ref().unwrap() {
match id.as_str() {
"a1" => (),
"b2" | "c3" => assert!(!pending_ids.contains(&"a1")),
"d4" | "e5" => assert!(!pending_ids.contains(&"b2")),
"f6" => assert!(!pending_ids.contains(&"d4")),
"g7" => assert!(!pending_ids.contains(&"e5")),
_ => panic!("Unexpected ID"),
}
pending_ids.retain(|i| i != id);
}
if !pending_ids.is_empty() {
panic!(
"Unexpected order: {:?}",
request
.create
.as_ref()
.unwrap()
.iter()
.map(|b| b.0.to_string())
.collect::<Vec<_>>()
);
}
} else if test_num == 2 {
assert_eq!(
request
.create
.unwrap()
.into_iter()
.map(|b| b.0)
.collect::<Vec<_>>(),
["x", "z", "y"]
.iter()
.map(|i| i.to_string())
.collect::<Vec<_>>()
);
}
} else {
panic!("Expected Set Mailbox Request");
}
}
let request = Request::parse(
br##"{
"using": [
"urn:ietf:params:jmap:core",
"urn:ietf:params:jmap:mail"
],
"methodCalls": [
[
"Mailbox/set",
{
"accountId": "b",
"create": {
"a": {
"name": "a",
"parentId": "#x"
},
"b": {
"name": "b",
"parentId": "#y"
},
"c": {
"name": "c",
"parentId": "#z"
}
}
},
"ref1"
],
[
"Mailbox/set",
{
"accountId": "b",
"create": {
"a1": {
"name": "a1",
"parentId": "#a"
},
"b2": {
"name": "b2",
"parentId": "#b"
},
"c3": {
"name": "c3",
"parentId": "#c"
}
}
},
"red2"
]
],
"createdIds": {
"x": "b",
"y": "c",
"z": "d"
}
}"##,
1024,
1024 * 1024,
)
.unwrap();
let mut response = Response::new(
1234,
request.created_ids.unwrap_or_default(),
request.method_calls.len(),
);
let mut invocations = request.method_calls.into_iter();
let mut call = invocations.next().unwrap();
response.resolve_references(&mut call.method).unwrap();
if let RequestMethod::Set(SetRequestMethod::Mailbox(request)) = call.method {
let create = request
.create
.as_ref()
.unwrap()
.iter()
.map(|(p, v)| {
(
p.as_str(),
v.as_object()
.unwrap()
.get(&Key::Property(MailboxProperty::ParentId))
.unwrap(),
)
})
.collect::<HashMap<_, _>>();
assert_eq!(
*create.get("a").unwrap(),
&Value::Element(MailboxValue::Id(Id::new(1)))
);
assert_eq!(
*create.get("b").unwrap(),
&Value::Element(MailboxValue::Id(Id::new(2)))
);
assert_eq!(
*create.get("c").unwrap(),
&Value::Element(MailboxValue::Id(Id::new(3)))
);
} else {
panic!("Expected Mailbox Set Request");
}
response
.created_ids
.insert("a".to_string(), Id::new(5).into());
response
.created_ids
.insert("b".to_string(), Id::new(6).into());
response
.created_ids
.insert("c".to_string(), Id::new(7).into());
let mut call = invocations.next().unwrap();
response.resolve_references(&mut call.method).unwrap();
if let RequestMethod::Set(SetRequestMethod::Mailbox(request)) = call.method {
let create = request
.create
.as_ref()
.unwrap()
.iter()
.map(|(p, v)| {
(
p.as_str(),
v.as_object()
.unwrap()
.get(&Key::Property(MailboxProperty::ParentId))
.unwrap(),
)
})
.collect::<HashMap<_, _>>();
assert_eq!(
*create.get("a1").unwrap(),
&Value::Element(MailboxValue::Id(Id::new(5)))
);
assert_eq!(
*create.get("b2").unwrap(),
&Value::Element(MailboxValue::Id(Id::new(6)))
);
assert_eq!(
*create.get("c3").unwrap(),
&Value::Element(MailboxValue::Id(Id::new(7)))
);
} else {
panic!("Expected Mailbox Set Request");
}
}
#[test]
fn eval_nested_element_ref() {
let mut created_ids = HashMap::new();
created_ids.insert("server-1".to_string(), Id::new(42).into());
let response = Response::new(0, created_ids, 0);
let mut value: Value<'_, MailboxProperty, MailboxValue> = Value::Object(Map::from(vec![
(
Key::Property(MailboxProperty::Name),
Value::Str("inbox".into()),
),
(
Key::Property(MailboxProperty::ParentId),
Value::Object(Map::from(vec![(
Key::Property(MailboxProperty::ParentId),
Value::Element(MailboxValue::IdReference("server-1".into())),
)])),
),
]));
value
.eval_object_references(&response, &mut Graph::None, 0, 5, true)
.unwrap();
let nested = value
.as_object()
.unwrap()
.get(&Key::Property(MailboxProperty::ParentId))
.unwrap()
.as_object()
.unwrap()
.get(&Key::Property(MailboxProperty::ParentId))
.unwrap();
assert_eq!(nested, &Value::Element(MailboxValue::Id(Id::new(42))));
}
#[test]
fn eval_array_element_ref() {
let mut created_ids = HashMap::new();
created_ids.insert("a".to_string(), Id::new(1).into());
created_ids.insert("b".to_string(), Id::new(2).into());
let response = Response::new(0, created_ids, 0);
let mut value: Value<'_, MailboxProperty, MailboxValue> = Value::Object(Map::from(vec![(
Key::Property(MailboxProperty::ParentId),
Value::Array(vec![
Value::Element(MailboxValue::IdReference("a".into())),
Value::Element(MailboxValue::IdReference("b".into())),
]),
)]));
value
.eval_object_references(&response, &mut Graph::None, 0, 5, true)
.unwrap();
let arr = value
.as_object()
.unwrap()
.get(&Key::Property(MailboxProperty::ParentId))
.unwrap()
.as_array()
.unwrap();
assert_eq!(arr.len(), 2);
assert_eq!(arr[0], Value::Element(MailboxValue::Id(Id::new(1))));
assert_eq!(arr[1], Value::Element(MailboxValue::Id(Id::new(2))));
}
#[test]
fn eval_nested_array_of_objects_with_element_ref() {
let mut created_ids = HashMap::new();
created_ids.insert("a".to_string(), Id::new(7).into());
let response = Response::new(0, created_ids, 0);
let mut value: Value<'_, MailboxProperty, MailboxValue> = Value::Object(Map::from(vec![(
Key::Property(MailboxProperty::ParentId),
Value::Array(vec![Value::Object(Map::from(vec![(
Key::Property(MailboxProperty::ParentId),
Value::Element(MailboxValue::IdReference("a".into())),
)]))]),
)]));
value
.eval_object_references(&response, &mut Graph::None, 0, 5, true)
.unwrap();
let resolved = value
.as_object()
.unwrap()
.get(&Key::Property(MailboxProperty::ParentId))
.unwrap()
.as_array()
.unwrap()[0]
.as_object()
.unwrap()
.get(&Key::Property(MailboxProperty::ParentId))
.unwrap();
assert_eq!(resolved, &Value::Element(MailboxValue::Id(Id::new(7))));
}
#[test]
fn eval_graph_collects_nested_ref() {
let response = Response::new(0, HashMap::new(), 0);
let mut graph_map: HashMap<String, Vec<String>> = HashMap::new();
let child_id = "outer".to_string();
let mut value: Value<'_, MailboxProperty, MailboxValue> = Value::Object(Map::from(vec![(
Key::Property(MailboxProperty::ParentId),
Value::Object(Map::from(vec![(
Key::Property(MailboxProperty::ParentId),
Value::Element(MailboxValue::IdReference("inner".into())),
)])),
)]));
{
let mut graph = Graph::Some {
child_id: &child_id,
graph: &mut graph_map,
};
value
.eval_object_references(&response, &mut graph, 0, 5, true)
.unwrap();
}
assert_eq!(graph_map.get("outer"), Some(&vec!["inner".to_string()]));
}
#[test]
fn eval_unresolved_nested_ref_errors_without_graph() {
let response = Response::new(0, HashMap::new(), 0);
let mut value: Value<'_, MailboxProperty, MailboxValue> = Value::Object(Map::from(vec![(
Key::Property(MailboxProperty::ParentId),
Value::Object(Map::from(vec![(
Key::Property(MailboxProperty::ParentId),
Value::Element(MailboxValue::IdReference("missing".into())),
)])),
)]));
let err = value
.eval_object_references(&response, &mut Graph::None, 0, 5, true)
.unwrap_err();
assert!(
err.matches(trc::EventType::Jmap(trc::JmapEvent::InvalidResultReference)),
"{:?}",
err
);
}
#[test]
fn eval_depth_limit_blocks_walk_into_inner_object() {
let mut created_ids = HashMap::new();
created_ids.insert("inner".to_string(), Id::new(99).into());
let response = Response::new(0, created_ids, 0);
let mut value: Value<'_, MailboxProperty, MailboxValue> = Value::Object(Map::from(vec![(
Key::Property(MailboxProperty::ParentId),
Value::Object(Map::from(vec![(
Key::Property(MailboxProperty::ParentId),
Value::Object(Map::from(vec![(
Key::Property(MailboxProperty::ParentId),
Value::Element(MailboxValue::IdReference("inner".into())),
)])),
)])),
)]));
value
.eval_object_references(&response, &mut Graph::None, 0, 2, true)
.unwrap();
let deepest = value
.as_object()
.unwrap()
.get(&Key::Property(MailboxProperty::ParentId))
.unwrap()
.as_object()
.unwrap()
.get(&Key::Property(MailboxProperty::ParentId))
.unwrap()
.as_object()
.unwrap()
.get(&Key::Property(MailboxProperty::ParentId))
.unwrap();
assert_eq!(
deepest,
&Value::Element(MailboxValue::IdReference("inner".into()))
);
}
#[test]
fn eval_depth_limit_substitutes_element_at_max_depth() {
let mut created_ids = HashMap::new();
created_ids.insert("inner".to_string(), Id::new(99).into());
let response = Response::new(0, created_ids, 0);
let mut value: Value<'_, MailboxProperty, MailboxValue> = Value::Object(Map::from(vec![(
Key::Property(MailboxProperty::ParentId),
Value::Object(Map::from(vec![(
Key::Property(MailboxProperty::ParentId),
Value::Element(MailboxValue::IdReference("inner".into())),
)])),
)]));
value
.eval_object_references(&response, &mut Graph::None, 0, 2, true)
.unwrap();
let resolved = value
.as_object()
.unwrap()
.get(&Key::Property(MailboxProperty::ParentId))
.unwrap()
.as_object()
.unwrap()
.get(&Key::Property(MailboxProperty::ParentId))
.unwrap();
assert_eq!(resolved, &Value::Element(MailboxValue::Id(Id::new(99))));
}
}
+433
View File
@@ -0,0 +1,433 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
error::set::SetError,
method::{
copy::CopyRequest,
get::GetRequest,
import::ImportEmailRequest,
parse::ParseRequest,
search_snippet::GetSearchSnippetRequest,
set::{SetRequest, SetResponse},
upload::{BlobUploadRequest, DataSourceObject},
},
object::{AnyId, JmapObject, JmapObjectId},
references::{Graph, eval::EvalObjectReferences, topological_sort},
request::{
CopyRequestMethod, GetRequestMethod, MaybeInvalid, ParseRequestMethod, RequestMethod,
SetRequestMethod,
reference::{MaybeIdReference, MaybeResultReference},
},
response::Response,
};
use compact_str::format_compact;
use jmap_tools::{Element, Key, Property, Value};
use std::collections::HashMap;
use types::id::Id;
impl Response<'_> {
pub fn resolve_references(&self, request: &mut RequestMethod) -> trc::Result<()> {
match request {
RequestMethod::Get(request) => match request {
GetRequestMethod::Email(request) => request.resolve_references(self)?,
GetRequestMethod::Mailbox(request) => request.resolve_references(self)?,
GetRequestMethod::Thread(request) => request.resolve_references(self)?,
GetRequestMethod::Identity(request) => request.resolve_references(self)?,
GetRequestMethod::EmailSubmission(request) => request.resolve_references(self)?,
GetRequestMethod::PushSubscription(request) => request.resolve_references(self)?,
GetRequestMethod::Sieve(request) => request.resolve_references(self)?,
GetRequestMethod::VacationResponse(request) => request.resolve_references(self)?,
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)?,
GetRequestMethod::FileNode(request) => request.resolve_references(self)?,
GetRequestMethod::ShareNotification(request) => request.resolve_references(self)?,
GetRequestMethod::Calendar(request) => request.resolve_references(self)?,
GetRequestMethod::CalendarEvent(request) => request.resolve_references(self)?,
GetRequestMethod::CalendarEventNotification(request) => {
request.resolve_references(self)?
}
GetRequestMethod::ParticipantIdentity(request) => {
request.resolve_references(self)?
}
GetRequestMethod::PrincipalAvailability(_) => (),
GetRequestMethod::Registry(request) => request.resolve_references(self)?,
},
RequestMethod::Set(request) => match request {
SetRequestMethod::Email(request) => request.resolve_references(self, 2, false)?,
SetRequestMethod::Mailbox(request) => request.resolve_references(self, 1, false)?,
SetRequestMethod::Identity(request) => {
request.resolve_references(self, 1, false)?
}
SetRequestMethod::EmailSubmission(request) => {
request.resolve_references(self, 1, false)?
}
SetRequestMethod::PushSubscription(request) => {
request.resolve_references(self, 1, false)?
}
SetRequestMethod::Sieve(request) => request.resolve_references(self, 1, false)?,
SetRequestMethod::VacationResponse(request) => {
request.resolve_references(self, 1, false)?
}
SetRequestMethod::AddressBook(request) => {
request.resolve_references(self, 1, false)?
}
SetRequestMethod::ContactCard(request) => {
request.resolve_references(self, 1, false)?
}
SetRequestMethod::FileNode(request) => {
request.resolve_references(self, 1, false)?
}
SetRequestMethod::ShareNotification(request) => {
request.resolve_references(self, 1, false)?
}
SetRequestMethod::Calendar(request) => {
request.resolve_references(self, 1, false)?
}
SetRequestMethod::CalendarEvent(request) => {
request.resolve_references(self, 1, false)?
}
SetRequestMethod::CalendarEventNotification(request) => {
request.resolve_references(self, 1, false)?
}
SetRequestMethod::ParticipantIdentity(request) => {
request.resolve_references(self, 1, false)?
}
SetRequestMethod::Registry(request) => request.resolve_references(self, 5, true)?,
},
RequestMethod::Copy(request) => match request {
CopyRequestMethod::Email(request) => request.resolve_references(self, 1, false)?,
CopyRequestMethod::CalendarEvent(request) => {
request.resolve_references(self, 1, false)?
}
CopyRequestMethod::ContactCard(request) => {
request.resolve_references(self, 1, false)?
}
CopyRequestMethod::FileNode(request) => {
request.resolve_references(self, 1, false)?
}
CopyRequestMethod::Blob(_) => (),
},
RequestMethod::ImportEmail(request) => request.resolve_references(self)?,
RequestMethod::SearchSnippet(request) => request.resolve_references(self)?,
RequestMethod::UploadBlob(request) => request.resolve_references(self)?,
RequestMethod::Parse(request) => match request {
ParseRequestMethod::Email(request) => request.resolve_references(self)?,
ParseRequestMethod::ContactCard(request) => request.resolve_references(self)?,
ParseRequestMethod::CalendarEvent(request) => request.resolve_references(self)?,
},
_ => {}
}
Ok(())
}
}
pub trait ResolveCreatedReference<P, E>
where
P: Property,
E: Element<Property = P> + JmapObjectId,
{
fn get_created_id(&self, id_ref: &str) -> Option<AnyId>;
fn resolve_self_references(
&self,
value: &mut Value<'_, P, E>,
depth: usize,
eval_strings: bool,
) -> Result<(), SetError<P>> {
match value {
Value::Object(obj) if eval_strings && depth < 5 => {
for (key, value) in obj.as_mut_vec() {
if let Some(id) = key
.as_string_key()
.and_then(|k| k.strip_prefix('#'))
.and_then(|id_ref| self.get_created_id(id_ref))
{
*key = Key::Owned(match id {
AnyId::Id(id) => id.to_string(),
AnyId::BlobId(id) => id.to_string(),
});
}
if matches!(value, Value::Object(_) | Value::Array(_)) {
self.resolve_self_references(value, depth + 1, eval_strings)?;
}
}
}
Value::Element(element) => {
if let Some(id_ref) = element.as_id_ref() {
if let Some(id) = self.get_created_id(id_ref) {
if !element.try_set_id(id) {
return Err(SetError::invalid_properties()
.with_description("Id reference points to invalid type."));
}
} else {
return Err(SetError::not_found()
.with_description(format!("Id reference {id_ref:?} not found.")));
}
}
}
Value::Array(items) if depth < 5 => {
for item in items {
self.resolve_self_references(item, depth + 1, eval_strings)?;
}
}
_ => {}
}
Ok(())
}
}
pub(crate) trait ResolveReference {
fn resolve_references(&mut self, response: &Response<'_>) -> trc::Result<()>;
}
pub(crate) trait ResolveSetReference {
fn resolve_references(
&mut self,
response: &Response<'_>,
max_depth: usize,
eval_strings: bool,
) -> trc::Result<()>;
}
impl<T: JmapObject> ResolveReference for GetRequest<T> {
fn resolve_references(&mut self, response: &Response<'_>) -> trc::Result<()> {
// Resolve id references
match &mut self.ids {
Some(MaybeResultReference::Reference(reference)) => {
self.ids = Some(MaybeResultReference::Value(
response
.eval_result_references(reference)?
.into_ids::<T::Id>(reference)
.map(|f| f.map(MaybeIdReference::Id))
.collect::<Result<_, _>>()?,
));
}
Some(MaybeResultReference::Value(ids)) => {
for id in ids {
if let MaybeIdReference::Reference(reference) = id {
if let Some(resolved_id) = response
.created_ids
.get(reference)
.cloned()
.and_then(|v| T::Id::try_from(v).ok())
{
*id = MaybeIdReference::Id(resolved_id);
} else {
return Err(trc::JmapEvent::InvalidResultReference.into_err().details(
format_compact!(
"Id reference {reference:?} does not exist or is invalid."
),
));
}
}
}
}
_ => (),
}
// Resolve properties references
if let Some(MaybeResultReference::Reference(reference)) = &self.properties {
self.properties = Some(MaybeResultReference::Value(
response
.eval_result_references(reference)?
.into_properties::<T::Property>(reference)
.map(|f| f.map(MaybeInvalid::Value))
.collect::<Result<_, _>>()?,
));
}
Ok(())
}
}
impl<'x, T: JmapObject> ResolveSetReference for SetRequest<'x, T> {
fn resolve_references(
&mut self,
response: &Response<'_>,
max_depth: usize,
eval_strings: bool,
) -> trc::Result<()> {
// Resolve create references
if let Some(create) = &mut self.create {
let mut graph = HashMap::with_capacity(create.len());
for (id, obj) in create.iter_mut() {
obj.eval_object_references(
response,
&mut Graph::Some {
child_id: &*id,
graph: &mut graph,
},
0,
max_depth,
eval_strings,
)?;
}
// Perform topological sort
if !graph.is_empty() {
self.create = topological_sort(create, graph)?.into();
}
}
// Resolve update references
if let Some(update) = &mut self.update {
for obj in update.values_mut() {
obj.eval_object_references(response, &mut Graph::None, 0, max_depth, eval_strings)?;
}
}
// Resolve destroy references
if let Some(MaybeResultReference::Reference(reference)) = &self.destroy {
self.destroy = Some(MaybeResultReference::Value(
response
.eval_result_references(reference)?
.into_ids::<Id>(reference)
.map(|f| f.map(MaybeInvalid::Value))
.collect::<Result<_, _>>()?,
));
}
Ok(())
}
}
impl<'x, T: JmapObject> ResolveSetReference for CopyRequest<'x, T> {
fn resolve_references(
&mut self,
response: &Response<'_>,
max_depth: usize,
eval_strings: bool,
) -> trc::Result<()> {
// Resolve create references
for (id, obj) in self.create.iter_mut() {
obj.eval_object_references(response, &mut Graph::None, 0, max_depth, eval_strings)?;
if let MaybeIdReference::Reference(ir) = id {
*id = MaybeIdReference::Id(response.eval_id_reference(ir)?);
}
}
Ok(())
}
}
impl<T: JmapObject> ResolveReference for ParseRequest<T> {
fn resolve_references(&mut self, response: &Response<'_>) -> trc::Result<()> {
// Resolve blobId references
for id in self.blob_ids.iter_mut() {
if let MaybeIdReference::Reference(ir) = id {
*id = MaybeIdReference::Id(response.eval_blob_id_reference(ir)?);
}
}
Ok(())
}
}
impl ResolveReference for ImportEmailRequest {
fn resolve_references(&mut self, response: &Response<'_>) -> trc::Result<()> {
// Resolve email mailbox references
for email in self.emails.values_mut() {
match &mut email.mailbox_ids {
MaybeResultReference::Reference(reference) => {
email.mailbox_ids = MaybeResultReference::Value(
response
.eval_result_references(reference)?
.into_ids::<Id>(reference)
.map(|f| f.map(MaybeIdReference::Id))
.collect::<Result<_, _>>()?,
);
}
MaybeResultReference::Value(values) => {
for value in values {
if let MaybeIdReference::Reference(ir) = value {
*value = MaybeIdReference::Id(response.eval_id_reference(ir)?);
}
}
}
}
}
Ok(())
}
}
impl ResolveReference for GetSearchSnippetRequest {
fn resolve_references(&mut self, response: &Response<'_>) -> trc::Result<()> {
// Resolve emailIds references
if let MaybeResultReference::Reference(reference) = &self.email_ids {
self.email_ids = MaybeResultReference::Value(
response
.eval_result_references(reference)?
.into_ids::<Id>(reference)
.map(|f| f.map(MaybeInvalid::Value))
.collect::<Result<_, _>>()?,
);
}
Ok(())
}
}
impl ResolveReference for BlobUploadRequest {
fn resolve_references(&mut self, response: &Response<'_>) -> trc::Result<()> {
let mut graph = HashMap::with_capacity(self.create.len());
for (create_id, object) in self.create.iter_mut() {
for data in &mut object.data {
if let DataSourceObject::Id { id, .. } = data
&& let MaybeIdReference::Reference(parent_id) = id
{
match response.created_ids.get(parent_id) {
Some(AnyId::BlobId(blob_id)) => {
*id = MaybeIdReference::Id(blob_id.clone());
}
Some(_) => {
return Err(trc::JmapEvent::InvalidResultReference.into_err().details(
format_compact!(
"Id reference {parent_id:?} points to invalid type."
),
));
}
None => {
graph
.entry(create_id.to_string())
.or_insert_with(Vec::new)
.push(parent_id.to_string());
}
}
}
}
}
// Perform topological sort
if !graph.is_empty() {
self.create = topological_sort(&mut self.create, graph)?;
}
Ok(())
}
}
impl<T> ResolveCreatedReference<T::Property, T::Element> for SetResponse<T>
where
T: JmapObject,
{
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())
}
}
+524
View File
@@ -0,0 +1,524 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::fmt;
use crate::{
object::{email::EmailComparator, file_node::FileNodeComparator},
response::serialize::serialize_hex,
types::date::UTCDate,
};
use ahash::AHashMap;
use serde::{Deserialize, Deserializer};
use types::{id::Id, type_state::DataType};
use utils::map::vec_map::VecMap;
#[derive(Debug, Clone, serde::Serialize)]
pub struct Session {
#[serde(rename(serialize = "capabilities"))]
pub capabilities: VecMap<Capability, Capabilities>,
#[serde(rename(serialize = "accounts"))]
pub accounts: VecMap<Id, Account>,
#[serde(rename(serialize = "primaryAccounts"))]
pub primary_accounts: VecMap<Capability, Id>,
#[serde(rename(serialize = "username"))]
pub username: String,
#[serde(rename(serialize = "apiUrl"))]
pub api_url: String,
#[serde(rename(serialize = "downloadUrl"))]
pub download_url: String,
#[serde(rename(serialize = "uploadUrl"))]
pub upload_url: String,
#[serde(rename(serialize = "eventSourceUrl"))]
pub event_source_url: String,
#[serde(rename(serialize = "state"))]
#[serde(serialize_with = "serialize_hex")]
pub state: u32,
#[serde(skip)]
pub base_url: String,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct Account {
#[serde(rename(serialize = "name"))]
pub name: String,
#[serde(rename(serialize = "isPersonal"))]
pub is_personal: bool,
#[serde(rename(serialize = "isReadOnly"))]
pub is_read_only: bool,
#[serde(rename(serialize = "accountCapabilities"))]
pub account_capabilities: VecMap<Capability, Capabilities>,
}
#[derive(Debug, Clone, Copy, serde::Serialize, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum Capability {
#[serde(rename(serialize = "urn:ietf:params:jmap:core"))]
Core = 1 << 0,
#[serde(rename(serialize = "urn:ietf:params:jmap:mail"))]
Mail = 1 << 1,
#[serde(rename(serialize = "urn:ietf:params:jmap:submission"))]
Submission = 1 << 2,
#[serde(rename(serialize = "urn:ietf:params:jmap:vacationresponse"))]
VacationResponse = 1 << 3,
#[serde(rename(serialize = "urn:ietf:params:jmap:contacts"))]
Contacts = 1 << 4,
#[serde(rename(serialize = "urn:ietf:params:jmap:contacts:parse"))]
ContactsParse = 1 << 5,
#[serde(rename(serialize = "urn:ietf:params:jmap:calendars"))]
Calendars = 1 << 6,
#[serde(rename(serialize = "urn:ietf:params:jmap:calendars:parse"))]
CalendarsParse = 1 << 7,
#[serde(rename(serialize = "urn:ietf:params:jmap:websocket"))]
WebSocket = 1 << 8,
#[serde(rename(serialize = "urn:ietf:params:jmap:sieve"))]
Sieve = 1 << 9,
#[serde(rename(serialize = "urn:ietf:params:jmap:blob"))]
Blob = 1 << 10,
#[serde(rename(serialize = "urn:ietf:params:jmap:quota"))]
Quota = 1 << 11,
#[serde(rename(serialize = "urn:ietf:params:jmap:principals"))]
Principals = 1 << 12,
#[serde(rename(serialize = "urn:ietf:params:jmap:principals:owner"))]
PrincipalsOwner = 1 << 13,
#[serde(rename(serialize = "urn:ietf:params:jmap:principals:availability"))]
PrincipalsAvailability = 1 << 14,
#[serde(rename(serialize = "urn:ietf:params:jmap:filenode"))]
FileNode = 1 << 15,
#[serde(rename(serialize = "urn:ietf:params:jmap:mail:share"))]
MailShare = 1 << 16,
#[serde(rename(serialize = "urn:stalwart:jmap"))]
Stalwart = 1 << 17,
#[serde(rename(serialize = "urn:ietf:params:jmap:webpush-vapid"))]
WebPushVapid = 1 << 18,
#[serde(rename(serialize = "urn:ietf:params:jmap:emailpush"))]
EmailPush = 1 << 19,
}
#[derive(Debug, Clone, Copy, Default)]
#[repr(transparent)]
pub struct CapabilityIds(pub u32);
impl CapabilityIds {
pub fn contains(&self, capability: Capability) -> bool {
self.0 & capability as u32 != 0
}
}
#[derive(Debug, Clone, serde::Serialize)]
#[serde(untagged)]
#[allow(dead_code)]
pub enum Capabilities {
Core(CoreCapabilities),
Mail(MailCapabilities),
Submission(SubmissionCapabilities),
WebSocket(WebSocketCapabilities),
SieveAccount(SieveAccountCapabilities),
SieveSession(SieveSessionCapabilities),
Blob(BlobCapabilities),
Contacts(ContactsCapabilities),
Principals(PrincipalCapabilities),
PrincipalsAvailability(PrincipalAvailabilityCapabilities),
Calendar(CalendarCapabilities),
FileNode(FileNodeCapabilities),
WebPush(WebPushCapabilities),
Empty(EmptyCapabilities),
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct CoreCapabilities {
#[serde(rename(serialize = "maxSizeUpload"))]
pub max_size_upload: u64,
#[serde(rename(serialize = "maxConcurrentUpload"))]
pub max_concurrent_upload: u64,
#[serde(rename(serialize = "maxSizeRequest"))]
pub max_size_request: u64,
#[serde(rename(serialize = "maxConcurrentRequests"))]
pub max_concurrent_requests: u64,
#[serde(rename(serialize = "maxCallsInRequest"))]
pub max_calls_in_request: u64,
#[serde(rename(serialize = "maxObjectsInGet"))]
pub max_objects_in_get: u64,
#[serde(rename(serialize = "maxObjectsInSet"))]
pub max_objects_in_set: u64,
#[serde(rename(serialize = "collationAlgorithms"))]
pub collation_algorithms: Vec<String>,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct WebSocketCapabilities {
#[serde(rename(serialize = "url"))]
pub url: String,
#[serde(rename(serialize = "supportsPush"))]
pub supports_push: bool,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct SieveSessionCapabilities {
#[serde(rename(serialize = "implementation"))]
pub implementation: &'static str,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct SieveAccountCapabilities {
#[serde(rename(serialize = "maxSizeScriptName"))]
pub max_script_name: u64,
#[serde(rename(serialize = "maxSizeScript"))]
pub max_script_size: u64,
#[serde(rename(serialize = "maxNumberScripts"))]
pub max_scripts: u64,
#[serde(rename(serialize = "maxNumberRedirects"))]
pub max_redirects: u64,
#[serde(rename(serialize = "sieveExtensions"))]
pub extensions: Vec<String>,
#[serde(rename(serialize = "notificationMethods"))]
pub notification_methods: Option<Vec<String>>,
#[serde(rename(serialize = "externalLists"))]
pub ext_lists: Option<Vec<String>>,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct MailCapabilities {
#[serde(rename(serialize = "maxMailboxesPerEmail"))]
pub max_mailboxes_per_email: Option<u64>,
#[serde(rename(serialize = "maxMailboxDepth"))]
pub max_mailbox_depth: u64,
#[serde(rename(serialize = "maxSizeMailboxName"))]
pub max_size_mailbox_name: u64,
#[serde(rename(serialize = "maxSizeAttachmentsPerEmail"))]
pub max_size_attachments_per_email: u64,
#[serde(rename(serialize = "emailQuerySortOptions"))]
pub email_query_sort_options: Vec<EmailComparator>,
#[serde(rename(serialize = "mayCreateTopLevelMailbox"))]
pub may_create_top_level_mailbox: bool,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct SubmissionCapabilities {
#[serde(rename(serialize = "maxDelayedSend"))]
pub max_delayed_send: u64,
#[serde(rename(serialize = "submissionExtensions"))]
pub submission_extensions: VecMap<String, Vec<String>>,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct BlobCapabilities {
#[serde(rename(serialize = "maxSizeBlobSet"))]
pub max_size_blob_set: u64,
#[serde(rename(serialize = "maxDataSources"))]
pub max_data_sources: u64,
#[serde(rename(serialize = "supportedTypeNames"))]
pub supported_type_names: Vec<DataType>,
#[serde(rename(serialize = "supportedDigestAlgorithms"))]
pub supported_digest_algorithms: Vec<&'static str>,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct CalendarCapabilities {
#[serde(rename(serialize = "maxCalendarsPerEvent"))]
pub max_calendars_per_event: Option<u64>,
#[serde(rename(serialize = "minDateTime"))]
pub min_date_time: UTCDate,
#[serde(rename(serialize = "maxDateTime"))]
pub max_date_time: UTCDate,
#[serde(rename(serialize = "maxExpandedQueryDuration"))]
pub max_expanded_query_duration: String,
#[serde(rename(serialize = "maxParticipantsPerEvent"))]
pub max_participants_per_event: Option<u64>,
#[serde(rename(serialize = "mayCreateCalendar"))]
pub may_create_calendar: bool,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct ContactsCapabilities {
#[serde(rename(serialize = "maxAddressBooksPerCard"))]
pub max_address_books_per_card: Option<u64>,
#[serde(rename(serialize = "mayCreateAddressBook"))]
pub may_create_address_book: bool,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct PrincipalAvailabilityCapabilities {
#[serde(rename(serialize = "maxAvailabilityDuration"))]
pub max_availability_duration: String,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct PrincipalCapabilities {
#[serde(rename(serialize = "currentUserPrincipalId"))]
pub current_user_principal_id: Option<Id>,
}
/*#[derive(Debug, Clone, serde::Serialize)]
pub struct PrincipalOwnerCapabilities {
#[serde(rename(serialize = "accountIdForPrincipal"))]
pub account_id_for_principal: Id,
#[serde(rename(serialize = "principalId"))]
pub principal_id: Id,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct PrincipalCalendarCapabilities {
#[serde(rename(serialize = "accountIdForPrincipal"))]
pub account_id_for_principal: Option<Id>,
#[serde(rename(serialize = "mayGetAvailability"))]
pub may_get_availability: bool,
#[serde(rename(serialize = "mayShareWith"))]
pub may_share_with: bool,
#[serde(rename(serialize = "calendarAddress"))]
pub calendar_address: String,
}*/
#[derive(Debug, Clone, serde::Serialize)]
pub struct FileNodeCapabilities {
#[serde(rename(serialize = "maxFileNodeDepth"))]
pub max_file_node_depth: Option<u64>,
#[serde(rename(serialize = "maxSizeFileNodeName"))]
pub max_size_file_node_name: u64,
#[serde(rename(serialize = "forbiddenNameChars"))]
pub forbidden_name_chars: Option<String>,
#[serde(rename(serialize = "forbiddenNodeNames"))]
pub forbidden_node_names: Option<Vec<String>>,
#[serde(rename(serialize = "fileNodeQuerySortOptions"))]
pub file_node_query_sort_options: Vec<FileNodeComparator>,
#[serde(rename(serialize = "mayCreateTopLevelFileNode"))]
pub may_create_top_level_file_node: bool,
#[serde(rename(serialize = "caseInsensitiveNames"))]
pub case_insensitive_names: bool,
#[serde(rename(serialize = "webTrashUrl"))]
pub web_trash_url: Option<String>,
#[serde(rename(serialize = "webUrlTemplate"))]
pub web_url_template: Option<String>,
#[serde(rename(serialize = "webWriteUrlTemplate"))]
pub web_write_url_template: Option<String>,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct WebPushCapabilities {
#[serde(rename(serialize = "applicationServerKey"))]
pub application_server_key: String,
}
#[derive(Debug, Clone, Default, serde::Serialize)]
pub struct EmptyCapabilities {}
#[derive(Default, Clone)]
pub struct BaseCapabilities {
pub session: VecMap<Capability, Capabilities>,
pub account: AHashMap<Capability, Capabilities>,
}
impl Capability {
pub fn as_str(&self) -> &'static str {
match self {
Capability::Core => "urn:ietf:params:jmap:core",
Capability::Mail => "urn:ietf:params:jmap:mail",
Capability::Submission => "urn:ietf:params:jmap:submission",
Capability::VacationResponse => "urn:ietf:params:jmap:vacationresponse",
Capability::Contacts => "urn:ietf:params:jmap:contacts",
Capability::ContactsParse => "urn:ietf:params:jmap:contacts:parse",
Capability::Calendars => "urn:ietf:params:jmap:calendars",
Capability::CalendarsParse => "urn:ietf:params:jmap:calendars:parse",
Capability::WebSocket => "urn:ietf:params:jmap:websocket",
Capability::Sieve => "urn:ietf:params:jmap:sieve",
Capability::Blob => "urn:ietf:params:jmap:blob",
Capability::Quota => "urn:ietf:params:jmap:quota",
Capability::Principals => "urn:ietf:params:jmap:principals",
Capability::PrincipalsOwner => "urn:ietf:params:jmap:principals:owner",
Capability::PrincipalsAvailability => "urn:ietf:params:jmap:principals:availability",
Capability::FileNode => "urn:ietf:params:jmap:filenode",
Capability::MailShare => "urn:ietf:params:jmap:mail:share",
Capability::Stalwart => "urn:stalwart:jmap",
Capability::WebPushVapid => "urn:ietf:params:jmap:webpush-vapid",
Capability::EmailPush => "urn:ietf:params:jmap:emailpush",
}
}
pub fn all_capabilities() -> &'static [Capability] {
&[
Capability::Core,
Capability::Mail,
Capability::Submission,
Capability::VacationResponse,
Capability::Contacts,
Capability::ContactsParse,
Capability::Calendars,
Capability::CalendarsParse,
Capability::WebSocket,
Capability::Sieve,
Capability::Blob,
Capability::Quota,
Capability::Principals,
Capability::PrincipalsAvailability,
Capability::FileNode,
Capability::MailShare,
Capability::Stalwart,
Capability::WebPushVapid,
Capability::EmailPush,
]
}
}
impl Session {
pub fn new(base_url: impl Into<String>, base_capabilities: &BaseCapabilities) -> Session {
let base_url = base_url.into();
let mut capabilities = base_capabilities.session.clone();
capabilities.append(
Capability::WebSocket,
Capabilities::WebSocket(WebSocketCapabilities::new(&base_url)),
);
Session {
capabilities,
accounts: VecMap::new(),
primary_accounts: VecMap::new(),
username: "".to_string(),
api_url: format!("{}/jmap/", base_url),
download_url: format!(
"{}/jmap/download/{{accountId}}/{{blobId}}/{{name}}?accept={{type}}",
base_url
),
upload_url: format!("{}/jmap/upload/{{accountId}}/", base_url),
event_source_url: format!(
"{}/jmap/eventsource/?types={{types}}&closeafter={{closeafter}}&ping={{ping}}",
base_url
),
base_url,
state: 0,
}
}
pub fn set_state(&mut self, state: u32) {
self.state = state;
}
pub fn api_url(&self) -> &str {
&self.api_url
}
pub fn base_url(&self) -> &str {
&self.base_url
}
}
impl Default for SieveSessionCapabilities {
fn default() -> Self {
Self {
implementation: "Stalwart v1.0.0",
}
}
}
impl WebSocketCapabilities {
pub fn new(base_url: &str) -> Self {
WebSocketCapabilities {
url: format!(
"ws{}/jmap/ws",
base_url.strip_prefix("http").unwrap_or_default()
),
supports_push: true,
}
}
}
impl Capabilities {
pub fn to_account_capabilities(
&self,
current_user_principal_id: Option<Id>,
may_create: bool,
) -> Capabilities {
match self {
Capabilities::Contacts(contacts_capabilities) => {
Capabilities::Contacts(ContactsCapabilities {
may_create_address_book: may_create,
..contacts_capabilities.clone()
})
}
Capabilities::Principals(_) => Capabilities::Principals(PrincipalCapabilities {
current_user_principal_id,
}),
Capabilities::Calendar(calendar_capabilities) => {
Capabilities::Calendar(CalendarCapabilities {
may_create_calendar: may_create,
..calendar_capabilities.clone()
})
}
Capabilities::FileNode(file_node_capabilities) => {
Capabilities::FileNode(FileNodeCapabilities {
may_create_top_level_file_node: may_create,
..file_node_capabilities.clone()
})
}
_ => self.clone(),
}
}
}
impl Capability {
pub fn parse(s: &str) -> Option<Self> {
hashify::tiny_map!(s.as_bytes(),
"urn:ietf:params:jmap:core" => Capability::Core,
"urn:ietf:params:jmap:mail" => Capability::Mail,
"urn:ietf:params:jmap:submission" => Capability::Submission,
"urn:ietf:params:jmap:vacationresponse" => Capability::VacationResponse,
"urn:ietf:params:jmap:contacts" => Capability::Contacts,
"urn:ietf:params:jmap:calendars" => Capability::Calendars,
"urn:ietf:params:jmap:websocket" => Capability::WebSocket,
"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,
"urn:ietf:params:jmap:filenode" => Capability::FileNode,
"urn:ietf:params:jmap:principals:availability" => Capability::PrincipalsAvailability,
"urn:ietf:params:jmap:contacts:parse" => Capability::ContactsParse,
"urn:ietf:params:jmap:calendars:parse" => Capability::CalendarsParse,
"urn:ietf:params:jmap:mail:share" => Capability::MailShare,
"urn:stalwart:jmap" => Capability::Stalwart,
"urn:ietf:params:jmap:webpush-vapid" => Capability::WebPushVapid,
"urn:ietf:params:jmap:emailpush" => Capability::EmailPush,
)
}
}
impl<'de> Deserialize<'de> for CapabilityIds {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct CapabilityIdsVisitor;
impl<'de> serde::de::Visitor<'de> for CapabilityIdsVisitor {
type Value = CapabilityIds;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("an array of capability strings")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let mut capability_flags = 0u32;
while let Some(capability_str) = seq.next_element::<std::borrow::Cow<str>>()? {
let capability =
Capability::parse(capability_str.as_ref()).ok_or_else(|| {
serde::de::Error::custom(format!(
"Unknown capability: {capability_str:?}"
))
})?;
capability_flags |= capability as u32;
}
Ok(CapabilityIds(capability_flags))
}
}
deserializer.deserialize_seq(CapabilityIdsVisitor)
}
}
@@ -0,0 +1,73 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use serde::{
Deserializer,
de::{self, MapAccess, Visitor},
};
use std::{fmt, marker::PhantomData};
pub trait DeserializeArguments<'de> {
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
A: MapAccess<'de>;
}
impl<'de> DeserializeArguments<'de> for () {
fn deserialize_argument<A>(&mut self, _key: &str, map: &mut A) -> Result<(), A::Error>
where
A: MapAccess<'de>,
{
let _: de::IgnoredAny = map.next_value()?;
Ok(())
}
}
pub(crate) fn deserialize_request<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where
T: DeserializeArguments<'de> + Default,
D: Deserializer<'de>,
{
struct DirectArgumentsVisitor<T> {
_phantom: PhantomData<T>,
}
impl<T> DirectArgumentsVisitor<T> {
fn new() -> Self {
Self {
_phantom: PhantomData,
}
}
}
impl<'de, T> Visitor<'de> for DirectArgumentsVisitor<T>
where
T: DeserializeArguments<'de> + Default,
{
type Value = T;
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: MapAccess<'de>,
{
let mut target = T::default();
while let Some(key) = map.next_key::<&str>()? {
target
.deserialize_argument(key, &mut map)
.map_err(de::Error::custom)?;
}
Ok(target)
}
}
deserializer.deserialize_map(DirectArgumentsVisitor::<T>::new())
}
+455
View File
@@ -0,0 +1,455 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::request::capability::Capability;
use registry::{
schema::prelude::{OBJ_SINGLETON, ObjectType},
types::EnumImpl,
};
use std::{borrow::Cow, fmt::Display};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MethodName {
pub obj: MethodObject,
pub fnc: MethodFunction,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MethodObject {
Email,
Mailbox,
Core,
Blob,
PushSubscription,
Thread,
SearchSnippet,
Identity,
EmailSubmission,
VacationResponse,
SieveScript,
Principal,
Quota,
Calendar,
CalendarEvent,
CalendarEventNotification,
AddressBook,
ContactCard,
FileNode,
ParticipantIdentity,
ShareNotification,
Registry(ObjectType),
}
impl MethodObject {
pub fn capability(&self) -> Capability {
match self {
MethodObject::Email
| MethodObject::Mailbox
| MethodObject::Thread
| MethodObject::SearchSnippet => Capability::Mail,
MethodObject::Core | MethodObject::PushSubscription => Capability::Core,
MethodObject::Blob => Capability::Blob,
MethodObject::Identity | MethodObject::EmailSubmission => Capability::Submission,
MethodObject::VacationResponse => Capability::VacationResponse,
MethodObject::SieveScript => Capability::Sieve,
MethodObject::Principal | MethodObject::ShareNotification => Capability::Principals,
MethodObject::Quota => Capability::Quota,
MethodObject::Calendar
| MethodObject::CalendarEvent
| MethodObject::CalendarEventNotification
| MethodObject::ParticipantIdentity => Capability::Calendars,
MethodObject::AddressBook | MethodObject::ContactCard => Capability::Contacts,
MethodObject::FileNode => Capability::FileNode,
MethodObject::Registry(_) => Capability::Stalwart,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MethodFunction {
Get,
Set,
Changes,
Query,
QueryChanges,
Copy,
Import,
Parse,
Validate,
Lookup,
Upload,
Echo,
GetAvailability,
}
impl Display for MethodName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str().as_ref())
}
}
impl MethodName {
pub fn new(obj: MethodObject, fnc: MethodFunction) -> Self {
Self { obj, fnc }
}
pub fn error() -> Self {
Self {
obj: MethodObject::Thread,
fnc: MethodFunction::Echo,
}
}
pub fn as_str(&self) -> Cow<'static, str> {
match (self.fnc, self.obj) {
(MethodFunction::Get, MethodObject::PushSubscription) => "PushSubscription/get",
(MethodFunction::Set, MethodObject::PushSubscription) => "PushSubscription/set",
(MethodFunction::Get, MethodObject::Mailbox) => "Mailbox/get",
(MethodFunction::Changes, MethodObject::Mailbox) => "Mailbox/changes",
(MethodFunction::Query, MethodObject::Mailbox) => "Mailbox/query",
(MethodFunction::QueryChanges, MethodObject::Mailbox) => "Mailbox/queryChanges",
(MethodFunction::Set, MethodObject::Mailbox) => "Mailbox/set",
(MethodFunction::Get, MethodObject::Thread) => "Thread/get",
(MethodFunction::Changes, MethodObject::Thread) => "Thread/changes",
(MethodFunction::Get, MethodObject::Email) => "Email/get",
(MethodFunction::Changes, MethodObject::Email) => "Email/changes",
(MethodFunction::Query, MethodObject::Email) => "Email/query",
(MethodFunction::QueryChanges, MethodObject::Email) => "Email/queryChanges",
(MethodFunction::Set, MethodObject::Email) => "Email/set",
(MethodFunction::Copy, MethodObject::Email) => "Email/copy",
(MethodFunction::Import, MethodObject::Email) => "Email/import",
(MethodFunction::Parse, MethodObject::Email) => "Email/parse",
(MethodFunction::Get, MethodObject::SearchSnippet) => "SearchSnippet/get",
(MethodFunction::Get, MethodObject::Identity) => "Identity/get",
(MethodFunction::Changes, MethodObject::Identity) => "Identity/changes",
(MethodFunction::Set, MethodObject::Identity) => "Identity/set",
(MethodFunction::Get, MethodObject::EmailSubmission) => "EmailSubmission/get",
(MethodFunction::Changes, MethodObject::EmailSubmission) => "EmailSubmission/changes",
(MethodFunction::Query, MethodObject::EmailSubmission) => "EmailSubmission/query",
(MethodFunction::QueryChanges, MethodObject::EmailSubmission) => {
"EmailSubmission/queryChanges"
}
(MethodFunction::Set, MethodObject::EmailSubmission) => "EmailSubmission/set",
(MethodFunction::Get, MethodObject::VacationResponse) => "VacationResponse/get",
(MethodFunction::Set, MethodObject::VacationResponse) => "VacationResponse/set",
(MethodFunction::Get, MethodObject::SieveScript) => "SieveScript/get",
(MethodFunction::Set, MethodObject::SieveScript) => "SieveScript/set",
(MethodFunction::Query, MethodObject::SieveScript) => "SieveScript/query",
(MethodFunction::Validate, MethodObject::SieveScript) => "SieveScript/validate",
(MethodFunction::Get, MethodObject::Principal) => "Principal/get",
(MethodFunction::Set, MethodObject::Principal) => "Principal/set",
(MethodFunction::Query, MethodObject::Principal) => "Principal/query",
(MethodFunction::Changes, MethodObject::Principal) => "Principal/changes",
(MethodFunction::QueryChanges, MethodObject::Principal) => "Principal/queryChanges",
(MethodFunction::GetAvailability, MethodObject::Principal) => {
"Principal/getAvailability"
}
(MethodFunction::Get, MethodObject::Quota) => "Quota/get",
(MethodFunction::Changes, MethodObject::Quota) => "Quota/changes",
(MethodFunction::Query, MethodObject::Quota) => "Quota/query",
(MethodFunction::QueryChanges, MethodObject::Quota) => "Quota/queryChanges",
(MethodFunction::Get, MethodObject::Blob) => "Blob/get",
(MethodFunction::Copy, MethodObject::Blob) => "Blob/copy",
(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::Query, MethodObject::AddressBook) => "AddressBook/query",
(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::Get, MethodObject::FileNode) => "FileNode/get",
(MethodFunction::Changes, MethodObject::FileNode) => "FileNode/changes",
(MethodFunction::Query, MethodObject::FileNode) => "FileNode/query",
(MethodFunction::QueryChanges, MethodObject::FileNode) => "FileNode/queryChanges",
(MethodFunction::Set, MethodObject::FileNode) => "FileNode/set",
(MethodFunction::Copy, MethodObject::FileNode) => "FileNode/copy",
(MethodFunction::Get, MethodObject::ShareNotification) => "ShareNotification/get",
(MethodFunction::Changes, MethodObject::ShareNotification) => {
"ShareNotification/changes"
}
(MethodFunction::Query, MethodObject::ShareNotification) => "ShareNotification/query",
(MethodFunction::QueryChanges, MethodObject::ShareNotification) => {
"ShareNotification/queryChanges"
}
(MethodFunction::Set, MethodObject::ShareNotification) => "ShareNotification/set",
(MethodFunction::Get, MethodObject::Calendar) => "Calendar/get",
(MethodFunction::Changes, MethodObject::Calendar) => "Calendar/changes",
(MethodFunction::Set, MethodObject::Calendar) => "Calendar/set",
(MethodFunction::Query, MethodObject::Calendar) => "Calendar/query",
(MethodFunction::Get, MethodObject::CalendarEvent) => "CalendarEvent/get",
(MethodFunction::Changes, MethodObject::CalendarEvent) => "CalendarEvent/changes",
(MethodFunction::Query, MethodObject::CalendarEvent) => "CalendarEvent/query",
(MethodFunction::QueryChanges, MethodObject::CalendarEvent) => {
"CalendarEvent/queryChanges"
}
(MethodFunction::Set, MethodObject::CalendarEvent) => "CalendarEvent/set",
(MethodFunction::Copy, MethodObject::CalendarEvent) => "CalendarEvent/copy",
(MethodFunction::Parse, MethodObject::CalendarEvent) => "CalendarEvent/parse",
(MethodFunction::Get, MethodObject::CalendarEventNotification) => {
"CalendarEventNotification/get"
}
(MethodFunction::Changes, MethodObject::CalendarEventNotification) => {
"CalendarEventNotification/changes"
}
(MethodFunction::Query, MethodObject::CalendarEventNotification) => {
"CalendarEventNotification/query"
}
(MethodFunction::QueryChanges, MethodObject::CalendarEventNotification) => {
"CalendarEventNotification/queryChanges"
}
(MethodFunction::Set, MethodObject::CalendarEventNotification) => {
"CalendarEventNotification/set"
}
(MethodFunction::Get, MethodObject::ParticipantIdentity) => "ParticipantIdentity/get",
(MethodFunction::Changes, MethodObject::ParticipantIdentity) => {
"ParticipantIdentity/changes"
}
(MethodFunction::Set, MethodObject::ParticipantIdentity) => "ParticipantIdentity/set",
(MethodFunction::Echo, MethodObject::Core) => "Core/echo",
(method, MethodObject::Registry(obj)) => {
return Cow::Owned(format!("x:{}/{}", obj.as_str(), method.as_str()));
}
_ => "error",
}
.into()
}
pub fn parse(s: &str) -> Option<Self> {
hashify::tiny_map!(s.as_bytes(),
"PushSubscription/get" => (MethodObject::PushSubscription, MethodFunction::Get),
"PushSubscription/set" => (MethodObject::PushSubscription, MethodFunction::Set),
"Mailbox/get" => (MethodObject::Mailbox, MethodFunction::Get),
"Mailbox/changes" => (MethodObject::Mailbox, MethodFunction::Changes),
"Mailbox/query" => (MethodObject::Mailbox, MethodFunction::Query),
"Mailbox/queryChanges" => (MethodObject::Mailbox, MethodFunction::QueryChanges),
"Mailbox/set" => (MethodObject::Mailbox, MethodFunction::Set),
"Thread/get" => (MethodObject::Thread, MethodFunction::Get),
"Thread/changes" => (MethodObject::Thread, MethodFunction::Changes),
"Email/get" => (MethodObject::Email, MethodFunction::Get),
"Email/changes" => (MethodObject::Email, MethodFunction::Changes),
"Email/query" => (MethodObject::Email, MethodFunction::Query),
"Email/queryChanges" => (MethodObject::Email, MethodFunction::QueryChanges),
"Email/set" => (MethodObject::Email, MethodFunction::Set),
"Email/copy" => (MethodObject::Email, MethodFunction::Copy),
"Email/import" => (MethodObject::Email, MethodFunction::Import),
"Email/parse" => (MethodObject::Email, MethodFunction::Parse),
"SearchSnippet/get" => (MethodObject::SearchSnippet, MethodFunction::Get),
"Identity/get" => (MethodObject::Identity, MethodFunction::Get),
"Identity/changes" => (MethodObject::Identity, MethodFunction::Changes),
"Identity/set" => (MethodObject::Identity, MethodFunction::Set),
"EmailSubmission/get" => (MethodObject::EmailSubmission, MethodFunction::Get),
"EmailSubmission/changes" => (MethodObject::EmailSubmission, MethodFunction::Changes),
"EmailSubmission/query" => (MethodObject::EmailSubmission, MethodFunction::Query),
"EmailSubmission/queryChanges" => (MethodObject::EmailSubmission, MethodFunction::QueryChanges),
"EmailSubmission/set" => (MethodObject::EmailSubmission, MethodFunction::Set),
"VacationResponse/get" => (MethodObject::VacationResponse, MethodFunction::Get),
"VacationResponse/set" => (MethodObject::VacationResponse, MethodFunction::Set),
"SieveScript/get" => (MethodObject::SieveScript, MethodFunction::Get),
"SieveScript/set" => (MethodObject::SieveScript, MethodFunction::Set),
"SieveScript/query" => (MethodObject::SieveScript, MethodFunction::Query),
"SieveScript/validate" => (MethodObject::SieveScript, MethodFunction::Validate),
"Principal/get" => (MethodObject::Principal, MethodFunction::Get),
"Principal/set" => (MethodObject::Principal, MethodFunction::Set),
"Principal/query" => (MethodObject::Principal, MethodFunction::Query),
"Principal/changes" => (MethodObject::Principal, MethodFunction::Changes),
"Principal/queryChanges" => (MethodObject::Principal, MethodFunction::QueryChanges),
"Principal/getAvailability" => (MethodObject::Principal, MethodFunction::GetAvailability),
"Quota/get" => (MethodObject::Quota, MethodFunction::Get),
"Quota/changes" => (MethodObject::Quota, MethodFunction::Changes),
"Quota/query" => (MethodObject::Quota, MethodFunction::Query),
"Quota/queryChanges" => (MethodObject::Quota, MethodFunction::QueryChanges),
"Blob/get" => (MethodObject::Blob, MethodFunction::Get),
"Blob/copy" => (MethodObject::Blob, MethodFunction::Copy),
"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),
"AddressBook/query" => (MethodObject::AddressBook, MethodFunction::Query),
"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),
"FileNode/get" => (MethodObject::FileNode, MethodFunction::Get),
"FileNode/changes" => (MethodObject::FileNode, MethodFunction::Changes),
"FileNode/query" => (MethodObject::FileNode, MethodFunction::Query),
"FileNode/queryChanges" => (MethodObject::FileNode, MethodFunction::QueryChanges),
"FileNode/set" => (MethodObject::FileNode, MethodFunction::Set),
"FileNode/copy" => (MethodObject::FileNode, MethodFunction::Copy),
"ShareNotification/get" => (MethodObject::ShareNotification, MethodFunction::Get),
"ShareNotification/changes" => (MethodObject::ShareNotification, MethodFunction::Changes),
"ShareNotification/set" => (MethodObject::ShareNotification, MethodFunction::Set),
"ShareNotification/query" => (MethodObject::ShareNotification, MethodFunction::Query),
"ShareNotification/queryChanges" => (MethodObject::ShareNotification, MethodFunction::QueryChanges),
"Calendar/get" => (MethodObject::Calendar, MethodFunction::Get),
"Calendar/changes" => (MethodObject::Calendar, MethodFunction::Changes),
"Calendar/set" => (MethodObject::Calendar, MethodFunction::Set),
"Calendar/query" => (MethodObject::Calendar, MethodFunction::Query),
"CalendarEvent/get" => (MethodObject::CalendarEvent, MethodFunction::Get),
"CalendarEvent/changes" => (MethodObject::CalendarEvent, MethodFunction::Changes),
"CalendarEvent/query" => (MethodObject::CalendarEvent, MethodFunction::Query),
"CalendarEvent/queryChanges" => (MethodObject::CalendarEvent, MethodFunction::QueryChanges),
"CalendarEvent/set" => (MethodObject::CalendarEvent, MethodFunction::Set),
"CalendarEvent/copy" => (MethodObject::CalendarEvent, MethodFunction::Copy),
"CalendarEvent/parse" => (MethodObject::CalendarEvent, MethodFunction::Parse),
"CalendarEventNotification/get" => (MethodObject::CalendarEventNotification, MethodFunction::Get),
"CalendarEventNotification/changes" => (MethodObject::CalendarEventNotification, MethodFunction::Changes),
"CalendarEventNotification/set" => (MethodObject::CalendarEventNotification, MethodFunction::Set),
"CalendarEventNotification/query" => (MethodObject::CalendarEventNotification, MethodFunction::Query),
"CalendarEventNotification/queryChanges" => (MethodObject::CalendarEventNotification, MethodFunction::QueryChanges),
"ParticipantIdentity/get" => (MethodObject::ParticipantIdentity, MethodFunction::Get),
"ParticipantIdentity/changes" => (MethodObject::ParticipantIdentity, MethodFunction::Changes),
"ParticipantIdentity/set" => (MethodObject::ParticipantIdentity, MethodFunction::Set),
"Core/echo" => (MethodObject::Core, MethodFunction::Echo),
).or_else(|| {
let (obj, fnc) = s.strip_prefix("x:")?.split_once('/')?;
let obj = ObjectType::parse(obj)?;
let fnc = hashify::tiny_map!(fnc.as_bytes(),
"get" => MethodFunction::Get,
"set" => MethodFunction::Set,
"query" => MethodFunction::Query,
)?;
if obj.flags() & OBJ_SINGLETON == 0 || fnc != MethodFunction::Query {
(MethodObject::Registry(obj), fnc).into()
} else {
None
}
}).map(|(obj, fnc)| MethodName { obj, fnc })
}
}
impl Display for MethodObject {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
MethodObject::Blob => "Blob",
MethodObject::EmailSubmission => "EmailSubmission",
MethodObject::SearchSnippet => "SearchSnippet",
MethodObject::Identity => "Identity",
MethodObject::VacationResponse => "VacationResponse",
MethodObject::PushSubscription => "PushSubscription",
MethodObject::SieveScript => "SieveScript",
MethodObject::Principal => "Principal",
MethodObject::Core => "Core",
MethodObject::Mailbox => "Mailbox",
MethodObject::Thread => "Thread",
MethodObject::Email => "Email",
MethodObject::Quota => "Quota",
MethodObject::AddressBook => "AddressBook",
MethodObject::ContactCard => "ContactCard",
MethodObject::FileNode => "FileNode",
MethodObject::ParticipantIdentity => "ParticipantIdentity",
MethodObject::Calendar => "Calendar",
MethodObject::CalendarEvent => "CalendarEvent",
MethodObject::CalendarEventNotification => "CalendarEventNotification",
MethodObject::ShareNotification => "ShareNotification",
MethodObject::Registry(obj) => {
f.write_str("x:")?;
return f.write_str(obj.as_str());
}
})
}
}
impl MethodFunction {
pub fn as_str(&self) -> &'static str {
match self {
MethodFunction::Get => "get",
MethodFunction::Set => "set",
MethodFunction::Changes => "changes",
MethodFunction::Query => "query",
MethodFunction::QueryChanges => "queryChanges",
MethodFunction::Copy => "copy",
MethodFunction::Import => "import",
MethodFunction::Parse => "parse",
MethodFunction::Validate => "validate",
MethodFunction::Lookup => "lookup",
MethodFunction::Upload => "upload",
MethodFunction::Echo => "echo",
MethodFunction::GetAvailability => "getAvailability",
}
}
}
impl MethodObject {
pub fn unwrap_registry(self) -> ObjectType {
match self {
MethodObject::Registry(obj) => obj,
_ => panic!("Not a registry method object"),
}
}
}
impl<'de> serde::Deserialize<'de> for MethodName {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = <Cow<str>>::deserialize(deserializer)?;
MethodName::parse(value.as_ref())
.ok_or_else(|| serde::de::Error::custom(format!("Invalid method name: {:?}", value)))
}
}
impl serde::Serialize for MethodName {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.as_str().as_ref())
}
}
+290
View File
@@ -0,0 +1,290 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
pub mod capability;
pub mod deserialize;
pub mod method;
pub mod parser;
pub mod reference;
pub mod websocket;
use self::method::MethodName;
use crate::{
method::{
availability::GetAvailabilityRequest,
changes::ChangesRequest,
copy::{CopyBlobRequest, CopyRequest},
get::GetRequest,
import::ImportEmailRequest,
lookup::BlobLookupRequest,
parse::ParseRequest,
query::QueryRequest,
query_changes::QueryChangesRequest,
search_snippet::GetSearchSnippetRequest,
set::SetRequest,
upload::BlobUploadRequest,
validate::ValidateSieveScriptRequest,
},
object::{
AnyId, addressbook::AddressBook, blob::Blob, calendar::Calendar,
calendar_event::CalendarEvent, calendar_event_notification::CalendarEventNotification,
contact::ContactCard, email::Email, email_submission::EmailSubmission, file_node::FileNode,
identity::Identity, mailbox::Mailbox, participant_identity::ParticipantIdentity,
principal::Principal, push_subscription::PushSubscription, quota::Quota,
registry::Registry, share_notification::ShareNotification, sieve::Sieve, thread::Thread,
vacation_response::VacationResponse,
},
request::{capability::CapabilityIds, reference::MaybeIdReference},
};
use jmap_tools::{Null, Value};
use std::{collections::HashMap, fmt::Debug, str::FromStr};
use types::id::Id;
use utils::map::vec_map::VecMap;
pub const INVALID_ACCOUNT_ID: u64 = u64::MAX - 1;
pub fn deserialize_account_id<'de, A>(map: &mut A) -> Result<Id, A::Error>
where
A: serde::de::MapAccess<'de>,
{
Ok(map
.next_value::<MaybeInvalid<Id>>()?
.try_unwrap()
.unwrap_or_else(|| Id::from(INVALID_ACCOUNT_ID)))
}
#[derive(Debug)]
pub struct Request<'x> {
pub using: CapabilityIds,
pub method_calls: Vec<Call<RequestMethod<'x>>>,
pub created_ids: Option<HashMap<String, AnyId>>,
}
#[derive(Debug)]
pub struct Call<T> {
pub id: String,
pub name: MethodName,
pub method: T,
}
#[derive(Debug)]
pub enum RequestMethod<'x> {
Get(GetRequestMethod),
Set(SetRequestMethod<'x>),
Changes(Box<ChangesRequest>),
Copy(CopyRequestMethod<'x>),
ImportEmail(Box<ImportEmailRequest>),
Parse(ParseRequestMethod),
Query(QueryRequestMethod),
QueryChanges(QueryChangesRequestMethod),
SearchSnippet(Box<GetSearchSnippetRequest>),
ValidateScript(Box<ValidateSieveScriptRequest>),
LookupBlob(Box<BlobLookupRequest>),
UploadBlob(Box<BlobUploadRequest>),
Echo(Value<'x, Null, Null>),
Error(trc::Error),
}
#[derive(Debug)]
pub enum GetRequestMethod {
Email(Box<GetRequest<Email>>),
Mailbox(Box<GetRequest<Mailbox>>),
Thread(Box<GetRequest<Thread>>),
Identity(Box<GetRequest<Identity>>),
EmailSubmission(Box<GetRequest<EmailSubmission>>),
PushSubscription(Box<GetRequest<PushSubscription>>),
Sieve(Box<GetRequest<Sieve>>),
VacationResponse(Box<GetRequest<VacationResponse>>),
Principal(Box<GetRequest<Principal>>),
PrincipalAvailability(Box<GetAvailabilityRequest>),
Quota(Box<GetRequest<Quota>>),
Blob(Box<GetRequest<Blob>>),
AddressBook(Box<GetRequest<AddressBook>>),
ContactCard(Box<GetRequest<ContactCard>>),
FileNode(Box<GetRequest<FileNode>>),
Calendar(Box<GetRequest<Calendar>>),
CalendarEvent(Box<GetRequest<CalendarEvent>>),
CalendarEventNotification(Box<GetRequest<CalendarEventNotification>>),
ParticipantIdentity(Box<GetRequest<ParticipantIdentity>>),
ShareNotification(Box<GetRequest<ShareNotification>>),
Registry(Box<GetRequest<Registry>>),
}
#[derive(Debug)]
pub enum SetRequestMethod<'x> {
Email(Box<SetRequest<'x, Email>>),
Mailbox(Box<SetRequest<'x, Mailbox>>),
Identity(Box<SetRequest<'x, Identity>>),
EmailSubmission(Box<SetRequest<'x, EmailSubmission>>),
PushSubscription(Box<SetRequest<'x, PushSubscription>>),
Sieve(Box<SetRequest<'x, Sieve>>),
VacationResponse(Box<SetRequest<'x, VacationResponse>>),
AddressBook(Box<SetRequest<'x, AddressBook>>),
ContactCard(Box<SetRequest<'x, ContactCard>>),
FileNode(Box<SetRequest<'x, FileNode>>),
ShareNotification(Box<SetRequest<'x, ShareNotification>>),
Calendar(Box<SetRequest<'x, Calendar>>),
CalendarEvent(Box<SetRequest<'x, CalendarEvent>>),
CalendarEventNotification(Box<SetRequest<'x, CalendarEventNotification>>),
ParticipantIdentity(Box<SetRequest<'x, ParticipantIdentity>>),
Registry(Box<SetRequest<'x, Registry>>),
}
#[derive(Debug)]
pub enum CopyRequestMethod<'x> {
Email(Box<CopyRequest<'x, Email>>),
ContactCard(Box<CopyRequest<'x, ContactCard>>),
CalendarEvent(Box<CopyRequest<'x, CalendarEvent>>),
FileNode(Box<CopyRequest<'x, FileNode>>),
Blob(Box<CopyBlobRequest>),
}
#[derive(Debug)]
pub enum QueryRequestMethod {
Email(Box<QueryRequest<Email>>),
Mailbox(Box<QueryRequest<Mailbox>>),
EmailSubmission(Box<QueryRequest<EmailSubmission>>),
Sieve(Box<QueryRequest<Sieve>>),
Principal(Box<QueryRequest<Principal>>),
Quota(Box<QueryRequest<Quota>>),
AddressBook(Box<QueryRequest<AddressBook>>),
ContactCard(Box<QueryRequest<ContactCard>>),
FileNode(Box<QueryRequest<FileNode>>),
Calendar(Box<QueryRequest<Calendar>>),
CalendarEvent(Box<QueryRequest<CalendarEvent>>),
CalendarEventNotification(Box<QueryRequest<CalendarEventNotification>>),
ShareNotification(Box<QueryRequest<ShareNotification>>),
Registry(Box<QueryRequest<Registry>>),
}
#[derive(Debug)]
pub enum QueryChangesRequestMethod {
Email(Box<QueryChangesRequest<Email>>),
Mailbox(Box<QueryChangesRequest<Mailbox>>),
EmailSubmission(Box<QueryChangesRequest<EmailSubmission>>),
Principal(Box<QueryChangesRequest<Principal>>),
Quota(Box<QueryChangesRequest<Quota>>),
ContactCard(Box<QueryChangesRequest<ContactCard>>),
FileNode(Box<QueryChangesRequest<FileNode>>),
CalendarEvent(Box<QueryChangesRequest<CalendarEvent>>),
CalendarEventNotification(Box<QueryChangesRequest<CalendarEventNotification>>),
ShareNotification(Box<QueryChangesRequest<ShareNotification>>),
}
#[derive(Debug)]
pub enum ParseRequestMethod {
Email(Box<ParseRequest<Email>>),
ContactCard(Box<ParseRequest<ContactCard>>),
CalendarEvent(Box<ParseRequest<CalendarEvent>>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MaybeInvalid<V: FromStr> {
Value(V),
Invalid(String),
}
impl<'de, V: FromStr> serde::Deserialize<'de> for MaybeInvalid<V> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = <&str>::deserialize(deserializer)?;
if let Ok(id) = V::from_str(value) {
Ok(MaybeInvalid::Value(id))
} else {
Ok(MaybeInvalid::Invalid(value.to_string()))
}
}
}
impl<V: FromStr + serde::Serialize> serde::Serialize for MaybeInvalid<V> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
MaybeInvalid::Value(v) => v.serialize(serializer),
MaybeInvalid::Invalid(s) => serializer.serialize_str(s),
}
}
}
impl<V: FromStr> From<V> for MaybeInvalid<V> {
fn from(value: V) -> Self {
MaybeInvalid::Value(value)
}
}
impl<V: FromStr> Default for MaybeInvalid<V> {
fn default() -> Self {
MaybeInvalid::Invalid("".to_string())
}
}
#[allow(clippy::derivable_impls)]
impl Default for Request<'_> {
fn default() -> Self {
Request {
using: CapabilityIds::default(),
method_calls: Vec::new(),
created_ids: None,
}
}
}
impl<T> MaybeInvalid<T>
where
T: FromStr,
{
pub fn try_unwrap(self) -> Option<T> {
match self {
MaybeInvalid::Value(id) => Some(id),
MaybeInvalid::Invalid(_) => None,
}
}
}
pub trait IntoValid {
type Item;
fn into_valid(self) -> impl Iterator<Item = Self::Item>;
}
impl<T: FromStr> IntoValid for Vec<MaybeInvalid<T>> {
type Item = T;
fn into_valid(self) -> impl Iterator<Item = Self::Item> {
self.into_iter().filter_map(|v| v.try_unwrap())
}
}
impl<T: FromStr> IntoValid for Vec<MaybeIdReference<T>> {
type Item = T;
fn into_valid(self) -> impl Iterator<Item = Self::Item> {
self.into_iter().filter_map(|v| v.try_unwrap())
}
}
impl<T: FromStr + Eq, V> IntoValid for VecMap<MaybeInvalid<T>, V> {
type Item = (T, V);
fn into_valid(self) -> impl Iterator<Item = Self::Item> {
self.into_iter()
.filter_map(|(k, v)| k.try_unwrap().map(|k| (k, v)))
}
}
impl<T: FromStr + Eq, V> IntoValid for VecMap<MaybeIdReference<T>, V> {
type Item = (T, V);
fn into_valid(self) -> impl Iterator<Item = Self::Item> {
self.into_iter()
.filter_map(|(k, v)| k.try_unwrap().map(|k| (k, v)))
}
}
+948
View File
@@ -0,0 +1,948 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
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::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::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));
}
},
(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(_)
));
}
}
+124
View File
@@ -0,0 +1,124 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::method::MethodName;
use jmap_tools::{JsonPointer, Null};
use std::{borrow::Cow, fmt::Display, str::FromStr};
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct ResultReference {
#[serde(rename = "resultOf")]
pub result_of: String,
pub name: MethodName,
pub path: JsonPointer<Null>,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum MaybeIdReference<V: FromStr> {
Id(V),
Reference(String),
Invalid(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MaybeResultReference<V> {
Value(V),
Reference(ResultReference),
}
impl Display for ResultReference {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{{ resultOf: {}, name: {}, path: {} }}",
self.result_of, self.name, self.path
)
}
}
impl<V: FromStr + Display> Display for MaybeIdReference<V> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MaybeIdReference::Id(id) => write!(f, "{}", id),
MaybeIdReference::Reference(str) => write!(f, "#{}", str),
MaybeIdReference::Invalid(str) => write!(f, "{}", str),
}
}
}
impl<'de, V: FromStr> serde::Deserialize<'de> for MaybeIdReference<V> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = <Cow<'de, str>>::deserialize(deserializer)?;
if let Some(reference) = value.strip_prefix('#') {
if reference.is_empty() {
return Ok(MaybeIdReference::Invalid(value.into_owned()));
}
Ok(MaybeIdReference::Reference(reference.to_string()))
} else if let Ok(id) = V::from_str(value.as_ref()) {
Ok(MaybeIdReference::Id(id))
} else {
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()))
}
}
}
impl<V: Display + FromStr> serde::Serialize for MaybeIdReference<V> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
MaybeIdReference::Id(id) => serializer.serialize_str(&id.to_string()),
MaybeIdReference::Reference(str) => serializer.serialize_str(&format!("#{}", str)),
MaybeIdReference::Invalid(str) => serializer.serialize_str(str),
}
}
}
impl<V: Default> Default for MaybeResultReference<V> {
fn default() -> Self {
MaybeResultReference::Value(V::default())
}
}
impl<T: Default> MaybeResultReference<T> {
pub fn unwrap(self) -> T {
match self {
MaybeResultReference::Value(v) => v,
MaybeResultReference::Reference(_) => T::default(),
}
}
}
impl<T: FromStr> MaybeIdReference<T> {
pub fn try_unwrap(self) -> Option<T> {
match self {
MaybeIdReference::Id(id) => Some(id),
_ => None,
}
}
}
+248
View File
@@ -0,0 +1,248 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::Request;
use crate::{
error::request::{RequestError, RequestErrorType, RequestLimitError},
object::AnyId,
request::{Call, deserialize::DeserializeArguments},
response::{Response, ResponseMethod, serialize::serialize_hex, status::PushObject},
};
use serde::{
Deserialize, Deserializer,
de::{self, MapAccess, Visitor},
};
use std::{borrow::Cow, collections::HashMap, fmt};
use types::type_state::DataType;
#[derive(Debug)]
pub struct WebSocketRequest<'x> {
pub id: Option<String>,
pub request: Request<'x>,
}
#[derive(Debug, serde::Serialize)]
pub struct WebSocketResponse<'x> {
#[serde(rename = "@type")]
_type: WebSocketResponseType,
#[serde(rename = "methodResponses")]
method_responses: Vec<Call<ResponseMethod<'x>>>,
#[serde(rename = "sessionState")]
#[serde(serialize_with = "serialize_hex")]
session_state: u32,
#[serde(rename(deserialize = "createdIds"))]
#[serde(skip_serializing_if = "HashMap::is_empty")]
created_ids: HashMap<String, AnyId>,
#[serde(rename = "requestId")]
#[serde(skip_serializing_if = "Option::is_none")]
request_id: Option<String>,
}
#[derive(Debug, PartialEq, Eq, serde::Serialize)]
pub enum WebSocketResponseType {
Response,
}
#[derive(Debug, Default, PartialEq, Eq)]
pub struct WebSocketPushEnable {
pub data_types: Vec<DataType>,
pub push_state: Option<String>,
}
#[derive(Debug)]
pub enum WebSocketMessage<'x> {
Request(WebSocketRequest<'x>),
PushEnable(WebSocketPushEnable),
PushDisable,
}
#[derive(serde::Serialize, Debug)]
pub struct WebSocketPushObject {
#[serde(flatten)]
pub push: PushObject,
#[serde(rename = "pushState")]
#[serde(skip_serializing_if = "Option::is_none")]
pub push_state: Option<String>,
}
#[derive(Debug, serde::Serialize)]
pub struct WebSocketRequestError<'x> {
#[serde(rename = "@type")]
pub type_: WebSocketRequestErrorType,
#[serde(rename = "type")]
p_type: RequestErrorType,
#[serde(skip_serializing_if = "Option::is_none")]
limit: Option<RequestLimitError>,
status: u16,
detail: Cow<'x, str>,
#[serde(rename = "requestId")]
#[serde(skip_serializing_if = "Option::is_none")]
pub request_id: Option<String>,
}
#[derive(serde::Serialize, Debug)]
pub enum WebSocketRequestErrorType {
RequestError,
}
enum MessageType {
Request,
PushEnable,
PushDisable,
None,
}
impl<'x> WebSocketMessage<'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::<Self>(json) {
Ok(WebSocketMessage::Request(req))
if req.request.method_calls.len() > max_calls =>
{
Err(trc::LimitEvent::CallsIn.into_err())
}
Ok(msg) => Ok(msg),
Err(err) => Err(trc::JmapEvent::NotRequest
.into_err()
.details(format!("Invalid WebSocket JMAP request {err}"))),
}
} else {
Err(trc::LimitEvent::SizeRequest.into_err())
}
}
}
impl<'de: 'x, 'x: 'de> Deserialize<'de> for WebSocketMessage<'x> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_map(WebSocketMessageVisitor)
}
}
struct WebSocketMessageVisitor;
impl<'de> Visitor<'de> for WebSocketMessageVisitor {
type Value = WebSocketMessage<'de>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a WebSocketMessage as a map")
}
fn visit_map<V>(self, mut map: V) -> Result<WebSocketMessage<'de>, V::Error>
where
V: MapAccess<'de>,
{
let mut message_type = MessageType::None;
let mut request = WebSocketRequest {
id: None,
request: Request::default(),
};
let mut push_enable = WebSocketPushEnable::default();
let mut found_request_keys = false;
let mut found_push_keys = false;
while let Some(key) = map.next_key::<&str>()? {
hashify::fnc_map!(key.as_bytes(),
b"@type" => {
message_type = MessageType::parse(map.next_value()?);
},
b"dataTypes" => {
push_enable.data_types = map.next_value::<Option<Vec<DataType>>>()?.unwrap_or_default();
found_push_keys = true;
},
b"pushState" => {
push_enable.push_state = map.next_value()?;
found_push_keys = true;
},
b"id" => {
request.id = map.next_value()?;
},
_ => {
request.request.deserialize_argument(key, &mut map)?;
found_request_keys = true;
}
);
}
match message_type {
MessageType::Request if found_request_keys => Ok(WebSocketMessage::Request(request)),
MessageType::PushEnable if found_push_keys => {
Ok(WebSocketMessage::PushEnable(push_enable))
}
MessageType::PushDisable if !found_request_keys && !found_push_keys => {
Ok(WebSocketMessage::PushDisable)
}
_ => Err(de::Error::custom("Invalid WebSocket JMAP request")),
}
}
}
impl MessageType {
fn parse(s: &str) -> Self {
hashify::tiny_map!(s.as_bytes(),
b"Request" => MessageType::Request,
b"WebSocketPushEnable" => MessageType::PushEnable,
b"WebSocketPushDisable" => MessageType::PushDisable,
)
.unwrap_or(MessageType::None)
}
}
impl<'x> WebSocketRequestError<'x> {
pub fn from_error(error: RequestError<'x>, request_id: Option<String>) -> Self {
Self {
type_: WebSocketRequestErrorType::RequestError,
p_type: error.p_type,
limit: error.limit,
status: error.status,
detail: error.detail,
request_id,
}
}
pub fn to_json(&self) -> String {
serde_json::to_string(self).unwrap()
}
}
impl<'x> From<RequestError<'x>> for WebSocketRequestError<'x> {
fn from(value: RequestError<'x>) -> Self {
Self::from_error(value, None)
}
}
impl<'x> WebSocketResponse<'x> {
pub fn from_response(response: Response<'x>, request_id: Option<String>) -> Self {
Self {
_type: WebSocketResponseType::Response,
method_responses: response.method_responses,
session_state: response.session_state,
created_ids: response.created_ids,
request_id,
}
}
pub fn to_json(&self) -> String {
serde_json::to_string(self).unwrap()
}
}
impl WebSocketPushObject {
pub fn to_json(&self) -> String {
serde_json::to_string(self).unwrap()
}
}
+616
View File
@@ -0,0 +1,616 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
pub mod serialize;
pub mod status;
use self::serialize::serialize_hex;
use crate::{
error::method::MethodErrorWrapper,
method::{
availability::GetAvailabilityResponse,
changes::ChangesResponse,
copy::{CopyBlobResponse, CopyResponse},
get::GetResponse,
import::ImportEmailResponse,
lookup::BlobLookupResponse,
parse::ParseResponse,
query::QueryResponse,
query_changes::QueryChangesResponse,
search_snippet::GetSearchSnippetResponse,
set::SetResponse,
upload::BlobUploadResponse,
validate::ValidateSieveScriptResponse,
},
object::{
AnyId,
addressbook::AddressBook,
blob::Blob,
calendar::Calendar,
calendar_event::CalendarEvent,
calendar_event_notification::{
CalendarEventNotification, CalendarEventNotificationGetResponse,
},
contact::ContactCard,
email::Email,
email_submission::EmailSubmission,
file_node::FileNode,
identity::Identity,
mailbox::Mailbox,
participant_identity::ParticipantIdentity,
principal::Principal,
push_subscription::PushSubscription,
quota::Quota,
registry::Registry,
share_notification::ShareNotification,
sieve::Sieve,
thread::Thread,
vacation_response::VacationResponse,
},
request::{Call, method::MethodName},
};
use jmap_tools::{Null, Value};
use std::collections::HashMap;
#[derive(Debug, serde::Serialize)]
#[serde(untagged)]
pub enum ResponseMethod<'x> {
Get(GetResponseMethod),
Set(SetResponseMethod),
Changes(ChangesResponseMethod),
Copy(CopyResponseMethod),
ImportEmail(ImportEmailResponse),
Parse(ParseResponseMethod),
QueryChanges(QueryChangesResponse),
Query(QueryResponse),
SearchSnippet(GetSearchSnippetResponse),
ValidateScript(ValidateSieveScriptResponse),
LookupBlob(BlobLookupResponse),
UploadBlob(BlobUploadResponse),
Echo(Value<'x, Null, Null>),
Error(MethodErrorWrapper),
}
#[derive(Debug, serde::Serialize)]
#[serde(untagged)]
pub enum GetResponseMethod {
Email(GetResponse<Email>),
Mailbox(GetResponse<Mailbox>),
Thread(GetResponse<Thread>),
Identity(GetResponse<Identity>),
EmailSubmission(GetResponse<EmailSubmission>),
PushSubscription(GetResponse<PushSubscription>),
Sieve(GetResponse<Sieve>),
VacationResponse(GetResponse<VacationResponse>),
Principal(GetResponse<Principal>),
PrincipalAvailability(GetAvailabilityResponse),
Quota(GetResponse<Quota>),
Blob(GetResponse<Blob>),
AddressBook(GetResponse<AddressBook>),
ContactCard(GetResponse<ContactCard>),
FileNode(GetResponse<FileNode>),
Calendar(GetResponse<Calendar>),
CalendarEvent(GetResponse<CalendarEvent>),
CalendarEventNotification(CalendarEventNotificationGetResponse),
ParticipantIdentity(GetResponse<ParticipantIdentity>),
ShareNotification(GetResponse<ShareNotification>),
Registry(GetResponse<Registry>),
}
#[derive(Debug, serde::Serialize)]
#[serde(untagged)]
pub enum SetResponseMethod {
Email(Box<SetResponse<Email>>),
Mailbox(Box<SetResponse<Mailbox>>),
Identity(Box<SetResponse<Identity>>),
EmailSubmission(Box<SetResponse<EmailSubmission>>),
PushSubscription(Box<SetResponse<PushSubscription>>),
Sieve(Box<SetResponse<Sieve>>),
VacationResponse(Box<SetResponse<VacationResponse>>),
AddressBook(Box<SetResponse<AddressBook>>),
ContactCard(Box<SetResponse<ContactCard>>),
FileNode(Box<SetResponse<FileNode>>),
ShareNotification(Box<SetResponse<ShareNotification>>),
Calendar(Box<SetResponse<Calendar>>),
CalendarEvent(Box<SetResponse<CalendarEvent>>),
CalendarEventNotification(Box<SetResponse<CalendarEventNotification>>),
ParticipantIdentity(Box<SetResponse<ParticipantIdentity>>),
Registry(Box<SetResponse<Registry>>),
}
#[derive(Debug, serde::Serialize)]
#[serde(untagged)]
pub enum ChangesResponseMethod {
Email(Box<ChangesResponse<Email>>),
Mailbox(Box<ChangesResponse<Mailbox>>),
Thread(Box<ChangesResponse<Thread>>),
Identity(Box<ChangesResponse<Identity>>),
EmailSubmission(Box<ChangesResponse<EmailSubmission>>),
Quota(Box<ChangesResponse<Quota>>),
AddressBook(Box<ChangesResponse<AddressBook>>),
ContactCard(Box<ChangesResponse<ContactCard>>),
FileNode(Box<ChangesResponse<FileNode>>),
Calendar(Box<ChangesResponse<Calendar>>),
CalendarEvent(Box<ChangesResponse<CalendarEvent>>),
CalendarEventNotification(Box<ChangesResponse<CalendarEventNotification>>),
ShareNotification(Box<ChangesResponse<ShareNotification>>),
}
#[derive(Debug, serde::Serialize)]
#[serde(untagged)]
pub enum CopyResponseMethod {
Email(CopyResponse<Email>),
ContactCard(CopyResponse<ContactCard>),
CalendarEvent(CopyResponse<CalendarEvent>),
FileNode(CopyResponse<FileNode>),
Blob(CopyBlobResponse),
}
#[derive(Debug, serde::Serialize)]
#[serde(untagged)]
pub enum ParseResponseMethod {
Email(ParseResponse<Email>),
ContactCard(ParseResponse<ContactCard>),
CalendarEvent(ParseResponse<CalendarEvent>),
}
#[derive(Debug, serde::Serialize)]
pub struct Response<'x> {
#[serde(rename = "methodResponses")]
pub method_responses: Vec<Call<ResponseMethod<'x>>>,
#[serde(rename = "sessionState")]
#[serde(serialize_with = "serialize_hex")]
pub session_state: u32,
#[serde(rename = "createdIds")]
#[serde(skip_serializing_if = "HashMap::is_empty")]
pub created_ids: HashMap<String, AnyId>,
}
impl<'x> Response<'x> {
pub fn new(session_state: u32, created_ids: HashMap<String, AnyId>, capacity: usize) -> Self {
Response {
session_state,
created_ids,
method_responses: Vec::with_capacity(capacity),
}
}
pub fn push_response(
&mut self,
id: String,
name: MethodName,
method: impl Into<ResponseMethod<'x>>,
) {
self.method_responses.push(Call {
id,
method: method.into(),
name,
});
}
pub fn push_error(&mut self, id: String, err: impl Into<MethodErrorWrapper>) {
self.method_responses.push(Call {
id,
method: ResponseMethod::Error(err.into()),
name: MethodName::error(),
});
}
pub fn push_created_id(&mut self, create_id: String, id: impl Into<AnyId>) {
self.created_ids.insert(create_id, id.into());
}
}
impl From<trc::Error> for ResponseMethod<'_> {
fn from(error: trc::Error) -> Self {
ResponseMethod::Error(error.into())
}
}
impl<'x, T: Into<ResponseMethod<'x>>> From<trc::Result<T>> for ResponseMethod<'x> {
fn from(result: trc::Result<T>) -> Self {
match result {
Ok(value) => value.into(),
Err(error) => error.into(),
}
}
}
impl<'x> From<GetResponse<Email>> for ResponseMethod<'x> {
fn from(value: GetResponse<Email>) -> Self {
ResponseMethod::Get(GetResponseMethod::Email(value))
}
}
impl<'x> From<GetResponse<Mailbox>> for ResponseMethod<'x> {
fn from(value: GetResponse<Mailbox>) -> Self {
ResponseMethod::Get(GetResponseMethod::Mailbox(value))
}
}
impl<'x> From<GetResponse<Thread>> for ResponseMethod<'x> {
fn from(value: GetResponse<Thread>) -> Self {
ResponseMethod::Get(GetResponseMethod::Thread(value))
}
}
impl<'x> From<GetResponse<Identity>> for ResponseMethod<'x> {
fn from(value: GetResponse<Identity>) -> Self {
ResponseMethod::Get(GetResponseMethod::Identity(value))
}
}
impl<'x> From<GetResponse<EmailSubmission>> for ResponseMethod<'x> {
fn from(value: GetResponse<EmailSubmission>) -> Self {
ResponseMethod::Get(GetResponseMethod::EmailSubmission(value))
}
}
impl<'x> From<GetResponse<PushSubscription>> for ResponseMethod<'x> {
fn from(value: GetResponse<PushSubscription>) -> Self {
ResponseMethod::Get(GetResponseMethod::PushSubscription(value))
}
}
impl<'x> From<GetResponse<Sieve>> for ResponseMethod<'x> {
fn from(value: GetResponse<Sieve>) -> Self {
ResponseMethod::Get(GetResponseMethod::Sieve(value))
}
}
impl<'x> From<GetResponse<VacationResponse>> for ResponseMethod<'x> {
fn from(value: GetResponse<VacationResponse>) -> Self {
ResponseMethod::Get(GetResponseMethod::VacationResponse(value))
}
}
impl<'x> From<GetResponse<Principal>> for ResponseMethod<'x> {
fn from(value: GetResponse<Principal>) -> Self {
ResponseMethod::Get(GetResponseMethod::Principal(value))
}
}
impl<'x> From<GetResponse<Quota>> for ResponseMethod<'x> {
fn from(value: GetResponse<Quota>) -> Self {
ResponseMethod::Get(GetResponseMethod::Quota(value))
}
}
impl<'x> From<GetResponse<Blob>> for ResponseMethod<'x> {
fn from(value: GetResponse<Blob>) -> Self {
ResponseMethod::Get(GetResponseMethod::Blob(value))
}
}
impl<'x> From<GetResponse<ContactCard>> for ResponseMethod<'x> {
fn from(value: GetResponse<ContactCard>) -> Self {
ResponseMethod::Get(GetResponseMethod::ContactCard(value))
}
}
impl<'x> From<GetResponse<AddressBook>> for ResponseMethod<'x> {
fn from(value: GetResponse<AddressBook>) -> Self {
ResponseMethod::Get(GetResponseMethod::AddressBook(value))
}
}
impl<'x> From<GetResponse<Registry>> for ResponseMethod<'x> {
fn from(value: GetResponse<Registry>) -> Self {
ResponseMethod::Get(GetResponseMethod::Registry(value))
}
}
impl<'x> From<SetResponse<Email>> for ResponseMethod<'x> {
fn from(value: SetResponse<Email>) -> Self {
ResponseMethod::Set(SetResponseMethod::Email(Box::new(value)))
}
}
impl<'x> From<SetResponse<Mailbox>> for ResponseMethod<'x> {
fn from(value: SetResponse<Mailbox>) -> Self {
ResponseMethod::Set(SetResponseMethod::Mailbox(Box::new(value)))
}
}
impl<'x> From<SetResponse<Identity>> for ResponseMethod<'x> {
fn from(value: SetResponse<Identity>) -> Self {
ResponseMethod::Set(SetResponseMethod::Identity(Box::new(value)))
}
}
impl<'x> From<SetResponse<EmailSubmission>> for ResponseMethod<'x> {
fn from(value: SetResponse<EmailSubmission>) -> Self {
ResponseMethod::Set(SetResponseMethod::EmailSubmission(Box::new(value)))
}
}
impl<'x> From<SetResponse<PushSubscription>> for ResponseMethod<'x> {
fn from(value: SetResponse<PushSubscription>) -> Self {
ResponseMethod::Set(SetResponseMethod::PushSubscription(Box::new(value)))
}
}
impl<'x> From<SetResponse<Sieve>> for ResponseMethod<'x> {
fn from(value: SetResponse<Sieve>) -> Self {
ResponseMethod::Set(SetResponseMethod::Sieve(Box::new(value)))
}
}
impl<'x> From<SetResponse<VacationResponse>> for ResponseMethod<'x> {
fn from(value: SetResponse<VacationResponse>) -> Self {
ResponseMethod::Set(SetResponseMethod::VacationResponse(Box::new(value)))
}
}
impl<'x> From<SetResponse<AddressBook>> for ResponseMethod<'x> {
fn from(value: SetResponse<AddressBook>) -> Self {
ResponseMethod::Set(SetResponseMethod::AddressBook(Box::new(value)))
}
}
impl<'x> From<SetResponse<ContactCard>> for ResponseMethod<'x> {
fn from(value: SetResponse<ContactCard>) -> Self {
ResponseMethod::Set(SetResponseMethod::ContactCard(Box::new(value)))
}
}
impl<'x> From<SetResponse<Registry>> for ResponseMethod<'x> {
fn from(value: SetResponse<Registry>) -> Self {
ResponseMethod::Set(SetResponseMethod::Registry(Box::new(value)))
}
}
impl<'x> From<ChangesResponse<Email>> for ResponseMethod<'x> {
fn from(value: ChangesResponse<Email>) -> Self {
ResponseMethod::Changes(ChangesResponseMethod::Email(Box::new(value)))
}
}
impl<'x> From<ChangesResponse<Mailbox>> for ResponseMethod<'x> {
fn from(value: ChangesResponse<Mailbox>) -> Self {
ResponseMethod::Changes(ChangesResponseMethod::Mailbox(Box::new(value)))
}
}
impl<'x> From<ChangesResponse<Thread>> for ResponseMethod<'x> {
fn from(value: ChangesResponse<Thread>) -> Self {
ResponseMethod::Changes(ChangesResponseMethod::Thread(Box::new(value)))
}
}
impl<'x> From<ChangesResponse<Identity>> for ResponseMethod<'x> {
fn from(value: ChangesResponse<Identity>) -> Self {
ResponseMethod::Changes(ChangesResponseMethod::Identity(Box::new(value)))
}
}
impl<'x> From<ChangesResponse<EmailSubmission>> for ResponseMethod<'x> {
fn from(value: ChangesResponse<EmailSubmission>) -> Self {
ResponseMethod::Changes(ChangesResponseMethod::EmailSubmission(Box::new(value)))
}
}
impl<'x> From<ChangesResponse<Quota>> for ResponseMethod<'x> {
fn from(value: ChangesResponse<Quota>) -> Self {
ResponseMethod::Changes(ChangesResponseMethod::Quota(Box::new(value)))
}
}
impl<'x> From<ChangesResponse<AddressBook>> for ResponseMethod<'x> {
fn from(value: ChangesResponse<AddressBook>) -> Self {
ResponseMethod::Changes(ChangesResponseMethod::AddressBook(Box::new(value)))
}
}
impl<'x> From<CopyResponse<Email>> for ResponseMethod<'x> {
fn from(value: CopyResponse<Email>) -> Self {
ResponseMethod::Copy(CopyResponseMethod::Email(value))
}
}
impl<'x> From<CopyBlobResponse> for ResponseMethod<'x> {
fn from(value: CopyBlobResponse) -> Self {
ResponseMethod::Copy(CopyResponseMethod::Blob(value))
}
}
impl<'x> From<CopyResponse<ContactCard>> for ResponseMethod<'x> {
fn from(value: CopyResponse<ContactCard>) -> Self {
ResponseMethod::Copy(CopyResponseMethod::ContactCard(value))
}
}
impl<'x> From<ImportEmailResponse> for ResponseMethod<'x> {
fn from(value: ImportEmailResponse) -> Self {
ResponseMethod::ImportEmail(value)
}
}
impl<'x> From<ParseResponse<Email>> for ResponseMethod<'x> {
fn from(value: ParseResponse<Email>) -> Self {
ResponseMethod::Parse(ParseResponseMethod::Email(value))
}
}
impl<'x> From<ParseResponse<ContactCard>> for ResponseMethod<'x> {
fn from(value: ParseResponse<ContactCard>) -> Self {
ResponseMethod::Parse(ParseResponseMethod::ContactCard(value))
}
}
impl<'x> From<QueryChangesResponse> for ResponseMethod<'x> {
fn from(value: QueryChangesResponse) -> Self {
ResponseMethod::QueryChanges(value)
}
}
impl<'x> From<QueryResponse> for ResponseMethod<'x> {
fn from(value: QueryResponse) -> Self {
ResponseMethod::Query(value)
}
}
impl<'x> From<GetSearchSnippetResponse> for ResponseMethod<'x> {
fn from(value: GetSearchSnippetResponse) -> Self {
ResponseMethod::SearchSnippet(value)
}
}
impl<'x> From<ValidateSieveScriptResponse> for ResponseMethod<'x> {
fn from(value: ValidateSieveScriptResponse) -> Self {
ResponseMethod::ValidateScript(value)
}
}
impl<'x> From<BlobLookupResponse> for ResponseMethod<'x> {
fn from(value: BlobLookupResponse) -> Self {
ResponseMethod::LookupBlob(value)
}
}
impl<'x> From<BlobUploadResponse> for ResponseMethod<'x> {
fn from(value: BlobUploadResponse) -> Self {
ResponseMethod::UploadBlob(value)
}
}
impl<'x> From<Value<'x, Null, Null>> for ResponseMethod<'x> {
fn from(value: Value<'x, Null, Null>) -> Self {
ResponseMethod::Echo(value)
}
}
impl<'x> From<MethodErrorWrapper> for ResponseMethod<'x> {
fn from(value: MethodErrorWrapper) -> Self {
ResponseMethod::Error(value)
}
}
impl From<GetResponse<FileNode>> for ResponseMethod<'_> {
fn from(response: GetResponse<FileNode>) -> Self {
ResponseMethod::Get(GetResponseMethod::FileNode(response))
}
}
impl From<SetResponse<FileNode>> for ResponseMethod<'_> {
fn from(response: SetResponse<FileNode>) -> Self {
ResponseMethod::Set(SetResponseMethod::FileNode(Box::new(response)))
}
}
impl From<ChangesResponse<FileNode>> for ResponseMethod<'_> {
fn from(response: ChangesResponse<FileNode>) -> Self {
ResponseMethod::Changes(ChangesResponseMethod::FileNode(Box::new(response)))
}
}
impl From<GetAvailabilityResponse> for ResponseMethod<'_> {
fn from(response: GetAvailabilityResponse) -> Self {
ResponseMethod::Get(GetResponseMethod::PrincipalAvailability(response))
}
}
impl From<GetResponse<Calendar>> for ResponseMethod<'_> {
fn from(response: GetResponse<Calendar>) -> Self {
ResponseMethod::Get(GetResponseMethod::Calendar(response))
}
}
impl From<SetResponse<Calendar>> for ResponseMethod<'_> {
fn from(response: SetResponse<Calendar>) -> Self {
ResponseMethod::Set(SetResponseMethod::Calendar(Box::new(response)))
}
}
impl From<ChangesResponse<CalendarEvent>> for ResponseMethod<'_> {
fn from(response: ChangesResponse<CalendarEvent>) -> Self {
ResponseMethod::Changes(ChangesResponseMethod::CalendarEvent(Box::new(response)))
}
}
impl From<ChangesResponse<CalendarEventNotification>> for ResponseMethod<'_> {
fn from(response: ChangesResponse<CalendarEventNotification>) -> Self {
ResponseMethod::Changes(ChangesResponseMethod::CalendarEventNotification(Box::new(
response,
)))
}
}
impl From<SetResponse<CalendarEvent>> for ResponseMethod<'_> {
fn from(response: SetResponse<CalendarEvent>) -> Self {
ResponseMethod::Set(SetResponseMethod::CalendarEvent(Box::new(response)))
}
}
impl From<SetResponse<ParticipantIdentity>> for ResponseMethod<'_> {
fn from(response: SetResponse<ParticipantIdentity>) -> Self {
ResponseMethod::Set(SetResponseMethod::ParticipantIdentity(Box::new(response)))
}
}
impl From<GetResponse<ParticipantIdentity>> for ResponseMethod<'_> {
fn from(response: GetResponse<ParticipantIdentity>) -> Self {
ResponseMethod::Get(GetResponseMethod::ParticipantIdentity(response))
}
}
impl From<ChangesResponse<ShareNotification>> for ResponseMethod<'_> {
fn from(response: ChangesResponse<ShareNotification>) -> Self {
ResponseMethod::Changes(ChangesResponseMethod::ShareNotification(Box::new(response)))
}
}
impl From<SetResponse<ShareNotification>> for ResponseMethod<'_> {
fn from(response: SetResponse<ShareNotification>) -> Self {
ResponseMethod::Set(SetResponseMethod::ShareNotification(Box::new(response)))
}
}
impl From<GetResponse<ShareNotification>> for ResponseMethod<'_> {
fn from(response: GetResponse<ShareNotification>) -> Self {
ResponseMethod::Get(GetResponseMethod::ShareNotification(response))
}
}
impl From<GetResponse<CalendarEvent>> for ResponseMethod<'_> {
fn from(response: GetResponse<CalendarEvent>) -> Self {
ResponseMethod::Get(GetResponseMethod::CalendarEvent(response))
}
}
impl From<ParseResponse<CalendarEvent>> for ResponseMethod<'_> {
fn from(value: ParseResponse<CalendarEvent>) -> Self {
ResponseMethod::Parse(ParseResponseMethod::CalendarEvent(value))
}
}
impl From<CopyResponse<CalendarEvent>> for ResponseMethod<'_> {
fn from(value: CopyResponse<CalendarEvent>) -> Self {
ResponseMethod::Copy(CopyResponseMethod::CalendarEvent(value))
}
}
impl From<CopyResponse<FileNode>> for ResponseMethod<'_> {
fn from(value: CopyResponse<FileNode>) -> Self {
ResponseMethod::Copy(CopyResponseMethod::FileNode(value))
}
}
impl From<CalendarEventNotificationGetResponse> for ResponseMethod<'_> {
fn from(value: CalendarEventNotificationGetResponse) -> Self {
ResponseMethod::Get(GetResponseMethod::CalendarEventNotification(value))
}
}
impl From<SetResponse<CalendarEventNotification>> for ResponseMethod<'_> {
fn from(value: SetResponse<CalendarEventNotification>) -> Self {
ResponseMethod::Set(SetResponseMethod::CalendarEventNotification(Box::new(
value,
)))
}
}
@@ -0,0 +1,29 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::ResponseMethod;
use crate::request::Call;
use serde::{Serialize, ser::SerializeSeq};
impl Serialize for Call<ResponseMethod<'_>> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut seq = serializer.serialize_seq(3.into())?;
seq.serialize_element(&self.name.to_string())?;
seq.serialize_element(&self.method)?;
seq.serialize_element(&self.id)?;
seq.end()
}
}
pub fn serialize_hex<S>(value: &u32, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
format!("{:x}", value).serialize(serializer)
}
+37
View File
@@ -0,0 +1,37 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::object::email::{EmailProperty, EmailValue};
use crate::types::state::State;
use jmap_tools::Value;
use types::{id::Id, type_state::DataType};
use utils::map::vec_map::VecMap;
#[derive(serde::Serialize, Debug)]
#[serde(tag = "@type")]
pub enum PushObject {
StateChange {
changed: VecMap<Id, VecMap<DataType, State>>,
},
EmailPush {
#[serde(rename = "accountId")]
account_id: Id,
emails: Vec<Value<'static, EmailProperty, EmailValue>>,
#[serde(skip_serializing_if = "Option::is_none")]
state: Option<State>,
},
CalendarAlert {
#[serde(rename = "accountId")]
account_id: Id,
#[serde(rename = "calendarEventId")]
calendar_event_id: Id,
uid: String,
#[serde(rename = "recurrenceId")]
recurrence_id: Option<String>,
#[serde(rename = "alertId")]
alert_id: String,
},
}
+278
View File
@@ -0,0 +1,278 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::{fmt::Display, str::FromStr};
#[derive(
rkyv::Archive,
rkyv::Serialize,
rkyv::Deserialize,
Debug,
Default,
Clone,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
)]
#[rkyv(derive(Debug), compare(PartialEq))]
pub struct UTCDate {
pub year: u16,
pub month: u8,
pub day: u8,
pub hour: u8,
pub minute: u8,
pub second: u8,
pub tz_before_gmt: bool,
pub tz_hour: u8,
pub tz_minute: u8,
}
impl FromStr for UTCDate {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
// 2004 - 06 - 28 T 23 : 43 : 45 . 000 Z
// 1969 - 02 - 13 T 23 : 32 : 00 - 03 : 30
// 0 1 2 3 4 5 6 7
let mut pos = 0;
let mut parts = [0u32; 8];
let mut parts_sizes = [
4u32, // Year (0)
2u32, // Month (1)
2u32, // Day (2)
2u32, // Hour (3)
2u32, // Minute (4)
2u32, // Second (5)
2u32, // TZ Hour (6)
2u32, // TZ Minute (7)
];
let mut skip_digits = false;
let mut is_plus = true;
for ch in s.as_bytes() {
match ch {
b'0'..=b'9' => {
if !skip_digits {
if parts_sizes[pos] > 0 {
parts_sizes[pos] -= 1;
parts[pos] += (ch - b'0') as u32 * u32::pow(10, parts_sizes[pos]);
} else {
break;
}
}
}
b'-' => {
if pos <= 1 {
pos += 1;
} else if pos == 5 {
pos += 1;
is_plus = false;
skip_digits = false;
} else {
break;
}
}
b'T' if pos == 2 => {
pos += 1;
}
b':' if [3, 4, 6].contains(&pos) => {
pos += 1;
}
b'+' if pos == 5 => {
pos += 1;
skip_digits = false;
}
b'.' if pos == 5 => {
skip_digits = true;
}
b'Z' | b'z' => (),
_ => {
break;
}
}
}
if pos >= 5 {
Ok(UTCDate {
year: parts[0] as u16,
month: parts[1] as u8,
day: parts[2] as u8,
hour: parts[3] as u8,
minute: parts[4] as u8,
second: parts[5] as u8,
tz_hour: parts[6] as u8,
tz_minute: parts[7] as u8,
tz_before_gmt: !is_plus,
})
} else {
Err(())
}
}
}
impl UTCDate {
pub fn from_timestamp(timestamp: i64) -> Self {
// Ported from http://howardhinnant.github.io/date_algorithms.html#civil_from_days
let (z, seconds) = ((timestamp / 86400) + 719468, timestamp % 86400);
let era: i64 = (if z >= 0 { z } else { z - 146096 }) / 146097;
let doe: u64 = (z - era * 146097) as u64; // [0, 146096]
let yoe: u64 = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; // [0, 399]
let y: i64 = (yoe as i64) + era * 400;
let doy: u64 = doe - (365 * yoe + yoe / 4 - yoe / 100); // [0, 365]
let mp = (5 * doy + 2) / 153; // [0, 11]
let d: u64 = doy - (153 * mp + 2) / 5 + 1; // [1, 31]
let m: u64 = if mp < 10 { mp + 3 } else { mp - 9 }; // [1, 12]
let (h, mn, s) = (seconds / 3600, (seconds / 60) % 60, seconds % 60);
UTCDate {
year: (y + i64::from(m <= 2)) as u16,
month: m as u8,
day: d as u8,
hour: h as u8,
minute: mn as u8,
second: s as u8,
tz_before_gmt: false,
tz_hour: 0,
tz_minute: 0,
}
}
pub fn is_valid(&self) -> bool {
(0..=23).contains(&self.tz_hour)
&& (1970..=3000).contains(&self.year)
&& (0..=59).contains(&self.tz_minute)
&& (1..=12).contains(&self.month)
&& (1..=31).contains(&self.day)
&& (0..=23).contains(&self.hour)
&& (0..=59).contains(&self.minute)
&& (0..=59).contains(&self.second)
}
pub fn timestamp(&self) -> i64 {
// Ported from https://github.com/protocolbuffers/upb/blob/22182e6e/upb/json_decode.c#L982-L992
let month = self.month as u32;
let year_base = 4800; /* Before min year, multiple of 400. */
let m_adj = month.wrapping_sub(3); /* March-based month. */
let carry = i64::from(m_adj > month);
let adjust = if carry > 0 { 12 } else { 0 };
let y_adj = self.year as i64 + year_base - carry;
let month_days = ((m_adj.wrapping_add(adjust)) * 62719 + 769) / 2048;
let leap_days = y_adj / 4 - y_adj / 100 + y_adj / 400;
(y_adj * 365 + leap_days + month_days as i64 + (self.day as i64 - 1) - 2472632) * 86400
+ self.hour as i64 * 3600
+ self.minute as i64 * 60
+ self.second as i64
+ ((self.tz_hour as i64 * 3600 + self.tz_minute as i64 * 60)
* if self.tz_before_gmt { 1 } else { -1 })
}
}
impl From<&ArchivedUTCDate> for UTCDate {
fn from(value: &ArchivedUTCDate) -> Self {
UTCDate {
year: value.year.to_native(),
month: value.month,
day: value.day,
hour: value.hour,
minute: value.minute,
second: value.second,
tz_before_gmt: value.tz_before_gmt,
tz_hour: value.tz_hour,
tz_minute: value.tz_minute,
}
}
}
impl Display for UTCDate {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.tz_hour != 0 || self.tz_minute != 0 {
write!(
f,
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}{}{:02}:{:02}",
self.year,
self.month,
self.day,
self.hour,
self.minute,
self.second,
if self.tz_before_gmt && (self.tz_hour > 0 || self.tz_minute > 0) {
"-"
} else {
"+"
},
self.tz_hour,
self.tz_minute,
)
} else {
write!(
f,
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
self.year, self.month, self.day, self.hour, self.minute, self.second,
)
}
}
}
impl serde::Serialize for UTCDate {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.to_string().as_str())
}
}
impl<'de> serde::Deserialize<'de> for UTCDate {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
UTCDate::from_str(<&str>::deserialize(deserializer)?)
.map_err(|_| serde::de::Error::custom("invalid JMAP UTCDate"))
}
}
impl From<UTCDate> for u64 {
fn from(value: UTCDate) -> Self {
value.timestamp() as u64
}
}
impl From<u64> for UTCDate {
fn from(value: u64) -> Self {
UTCDate::from_timestamp(value as i64)
}
}
#[cfg(test)]
mod tests {
use crate::types::date::UTCDate;
use std::str::FromStr;
#[test]
fn parse_jmap_date() {
for (input, expected_result) in [
("1997-11-21T09:55:06-06:00", "1997-11-21T09:55:06-06:00"),
("1997-11-21T09:55:06+00:00", "1997-11-21T09:55:06Z"),
("2021-01-01T09:55:06+02:00", "2021-01-01T09:55:06+02:00"),
("2004-06-28T23:43:45.000Z", "2004-06-28T23:43:45Z"),
("1997-11-21T09:55:06.123+00:00", "1997-11-21T09:55:06Z"),
(
"2021-01-01T09:55:06.4567+02:00",
"2021-01-01T09:55:06+02:00",
),
] {
let date = UTCDate::from_str(input).unwrap();
assert_eq!(date.to_string(), expected_result);
let timestamp = date.timestamp();
assert_eq!(UTCDate::from_timestamp(timestamp).timestamp(), timestamp);
}
}
}
+8
View File
@@ -0,0 +1,8 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
pub mod date;
pub mod state;
+177
View File
@@ -0,0 +1,177 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use types::ChangeId;
use utils::codec::{
base32_custom::{Base32Reader, Base32Writer},
leb128::{Leb128Iterator, Leb128Writer},
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct JMAPIntermediateState {
pub from_id: ChangeId,
pub to_id: ChangeId,
pub items_sent: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum State {
#[default]
Initial,
Exact(ChangeId),
Intermediate(JMAPIntermediateState),
}
impl From<Option<ChangeId>> for State {
fn from(change_id: Option<ChangeId>) -> Self {
match change_id {
Some(change_id) => State::Exact(change_id),
None => State::Initial,
}
}
}
impl State {
pub fn parse(value: &str) -> Option<Self> {
let mut it = value.as_bytes().iter();
match it.next()? {
b'n' => Some(State::Initial),
b's' => {
let mut reader = Base32Reader::from_iter(it);
reader
.next_leb128::<ChangeId>()
.map(|change_id| (change_id != 0).then_some(change_id).into())
}
b'r' => {
let mut it = Base32Reader::from_iter(it);
if let (Some(from_id), Some(to_id), Some(items_sent)) = (
it.next_leb128::<ChangeId>(),
it.next_leb128::<ChangeId>(),
it.next_leb128::<usize>(),
) {
if items_sent > 0 {
Some(State::Intermediate(JMAPIntermediateState {
from_id,
to_id: from_id.saturating_add(to_id),
items_sent,
}))
} else {
None
}
} else {
None
}
}
_ => None,
}
}
pub fn new_initial() -> Self {
State::Initial
}
pub fn new_exact(id: ChangeId) -> Self {
State::Exact(id)
}
pub fn new_intermediate(from_id: ChangeId, to_id: ChangeId, items_sent: usize) -> Self {
State::Intermediate(JMAPIntermediateState {
from_id,
to_id,
items_sent,
})
}
pub fn get_change_id(&self) -> ChangeId {
match self {
State::Exact(id) => *id,
State::Intermediate(intermediate) => intermediate.to_id,
State::Initial => ChangeId::MAX,
}
}
}
impl serde::Serialize for State {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.to_string().as_str())
}
}
impl<'de> serde::Deserialize<'de> for State {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
State::parse(<&str>::deserialize(deserializer)?)
.ok_or_else(|| serde::de::Error::custom("invalid JMAP State"))
}
}
impl std::fmt::Display for State {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut writer = Base32Writer::with_capacity(10);
match self {
State::Initial => {
writer.push_char('n');
}
State::Exact(id) => {
writer.push_char('s');
writer.write_leb128(*id).unwrap();
}
State::Intermediate(intermediate) => {
writer.push_char('r');
writer.write_leb128(intermediate.from_id).unwrap();
writer
.write_leb128(intermediate.to_id - intermediate.from_id)
.unwrap();
writer.write_leb128(intermediate.items_sent).unwrap();
}
}
f.write_str(&writer.finalize())
}
}
#[cfg(test)]
mod tests {
use super::State;
use types::ChangeId;
#[test]
fn test_state_id() {
for id in [
State::new_initial(),
State::new_exact(1),
State::new_exact(12345678),
State::new_exact(ChangeId::MAX),
State::new_intermediate(0, 0, 1),
State::new_intermediate(1024, 2048, 100),
State::new_intermediate(12345678, 87654321, 1),
State::new_intermediate(0, 0, 12345678),
State::new_intermediate(0, 87654321, 12345678),
State::new_intermediate(12345678, 87654321, 1),
State::new_intermediate(12345678, 87654321, 12345678),
State::new_intermediate(ChangeId::MAX, ChangeId::MAX, ChangeId::MAX as usize),
] {
assert_eq!(State::parse(&id.to_string()).unwrap(), id);
}
}
#[test]
fn test_state_zero_change_id_is_initial() {
assert_eq!(
State::parse(&State::new_exact(0).to_string()).unwrap(),
State::Initial
);
assert_eq!(State::from(None), State::Initial);
}
}