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:
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()),
|
||||
)])),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user