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
@@ -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)
}
}