mirror of
https://github.com/stalwartlabs/stalwart.git
synced 2026-10-30 00:15:29 +00:00
JMAP protocol layer refactoring (part 3)
This commit is contained in:
@@ -13,9 +13,9 @@ use crate::{
|
||||
},
|
||||
types::state::State,
|
||||
};
|
||||
use jmap_tools::{Property, Value};
|
||||
use jmap_tools::Value;
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use types::{blob::BlobId, id::Id};
|
||||
use types::id::Id;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GetRequest<T: JmapObject> {
|
||||
@@ -90,14 +90,26 @@ impl<T: JmapObject> Default for GetRequest<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
impl<T: JmapObject> GetRequest<T> {
|
||||
pub fn unwrap_properties(&mut self, default: &[T::Property]) -> Vec<T::Property> {
|
||||
if let Some(mut properties) = self.properties.take().map(|p| p.unwrap()) {
|
||||
// Add Id Property
|
||||
if !properties.contains(&Property::Id) {
|
||||
properties.push(Property::Id);
|
||||
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()
|
||||
@@ -110,27 +122,7 @@ impl<T: JmapObject> GetRequest<T> {
|
||||
if ids.len() <= max_objects_in_get {
|
||||
Ok(Some(
|
||||
ids.into_iter()
|
||||
.filter_map(|id| id.try_unwrap().and_then(|id| id.into_id()))
|
||||
.collect::<Vec<_>>(),
|
||||
))
|
||||
} else {
|
||||
Err(trc::JmapEvent::RequestTooLarge.into_err())
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unwrap_blob_ids(
|
||||
&mut self,
|
||||
max_objects_in_get: usize,
|
||||
) -> trc::Result<Option<Vec<BlobId>>> {
|
||||
if let Some(ids) = self.ids.take() {
|
||||
let ids = ids.unwrap();
|
||||
if ids.len() <= max_objects_in_get {
|
||||
Ok(Some(
|
||||
ids.into_iter()
|
||||
.filter_map(|id| id.try_unwrap().and_then(|id| id.into_blob_id()))
|
||||
.filter_map(|id| id.try_unwrap())
|
||||
.collect::<Vec<_>>(),
|
||||
))
|
||||
} else {
|
||||
@@ -141,4 +133,3 @@ impl<T: JmapObject> GetRequest<T> {
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -5,18 +5,20 @@
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
object::{JmapObject, email, mailbox},
|
||||
request::{
|
||||
deserialize::{DeserializeArguments, deserialize_request},
|
||||
method::MethodObject,
|
||||
},
|
||||
types::{date::UTCDate, state::State},
|
||||
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, Display, Formatter},
|
||||
};
|
||||
use compact_str::format_compact;
|
||||
use serde::{Deserialize, Deserializer, de::DeserializeOwned};
|
||||
use std::fmt::Display;
|
||||
use store::fts::{FilterItem, FilterType, FtsFilter};
|
||||
use types::{id::Id, keyword::Keyword};
|
||||
use types::{id::Id};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct QueryRequest<T: JmapObject> {
|
||||
@@ -57,95 +59,26 @@ pub struct QueryResponse {
|
||||
pub limit: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub enum Filter<T> {
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Filter<T>
|
||||
where
|
||||
T: for<'de> DeserializeArguments<'de> + Default,
|
||||
{
|
||||
Property(T),
|
||||
|
||||
And,
|
||||
Or,
|
||||
Not,
|
||||
Close,
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Email(String),
|
||||
Name(String),
|
||||
DomainName(String),
|
||||
Text(String),
|
||||
Type(String),
|
||||
Timezone(String),
|
||||
Members(Id),
|
||||
QuotaLt(u32),
|
||||
QuotaGt(u32),
|
||||
IdentityIds(Vec<Id>),
|
||||
EmailIds(Vec<Id>),
|
||||
ThreadIds(Vec<Id>),
|
||||
UndoStatus(String),
|
||||
Before(UTCDate),
|
||||
After(UTCDate),
|
||||
InMailbox(Id),
|
||||
InMailboxOtherThan(Vec<Id>),
|
||||
MinSize(u32),
|
||||
MaxSize(u32),
|
||||
AllInThreadHaveKeyword(Keyword),
|
||||
SomeInThreadHaveKeyword(Keyword),
|
||||
NoneInThreadHaveKeyword(Keyword),
|
||||
HasKeyword(Keyword),
|
||||
NotKeyword(Keyword),
|
||||
HasAttachment(bool),
|
||||
From(String),
|
||||
To(String),
|
||||
Cc(String),
|
||||
Bcc(String),
|
||||
Subject(String),
|
||||
Body(String),
|
||||
Header(Vec<String>),
|
||||
Id(Vec<Id>),
|
||||
SentBefore(UTCDate),
|
||||
SentAfter(UTCDate),
|
||||
InThread(Id),
|
||||
ParentId(Option<Id>),
|
||||
Role(Option<String>),
|
||||
HasAnyRole(bool),
|
||||
IsSubscribed(bool),
|
||||
IsActive(bool),
|
||||
Scope(String),
|
||||
ResourceType(String),
|
||||
_T(String),
|
||||
|
||||
*/
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
||||
pub struct Comparator<T> {
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||
pub struct Comparator<T>
|
||||
where
|
||||
T: for<'de> DeserializeArguments<'de> + Default,
|
||||
{
|
||||
pub is_ascending: bool,
|
||||
pub collation: Option<String>,
|
||||
pub property: T,
|
||||
//pub keyword: Option<Keyword>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum SortProperty {
|
||||
Type,
|
||||
Name,
|
||||
Email,
|
||||
EmailId,
|
||||
ThreadId,
|
||||
SentAt,
|
||||
ReceivedAt,
|
||||
Size,
|
||||
From,
|
||||
To,
|
||||
Subject,
|
||||
Cc,
|
||||
SortOrder,
|
||||
ParentId,
|
||||
IsActive,
|
||||
HasKeyword,
|
||||
AllInThreadHaveKeyword,
|
||||
SomeInThreadHaveKeyword,
|
||||
Used,
|
||||
_T(String),
|
||||
}
|
||||
|
||||
impl<'de, T: JmapObject> DeserializeArguments<'de> for QueryRequest<T> {
|
||||
@@ -158,7 +91,7 @@ impl<'de, T: JmapObject> DeserializeArguments<'de> for QueryRequest<T> {
|
||||
self.account_id = map.next_value()?;
|
||||
},
|
||||
b"filter" => {
|
||||
self.filter = map.next_value()?;
|
||||
self.filter = map.next_value::<FilterWrapper<T::Filter>>()?.0;
|
||||
},
|
||||
b"sort" => {
|
||||
self.sort = map.next_value()?;
|
||||
@@ -212,491 +145,194 @@ impl<T: JmapObject> Default for QueryRequest<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
pub fn parse_filter(parser: &mut Parser) -> trc::Result<Vec<Filter>> {
|
||||
let mut filter = vec![Filter::Close];
|
||||
let mut pos_stack = vec![0];
|
||||
struct FilterMapCollector<'x, T: 'x>(&'x mut Vec<Filter<T>>)
|
||||
where
|
||||
T: for<'de> DeserializeArguments<'de> + Default;
|
||||
|
||||
loop {
|
||||
match parser.next_token::<RequestProperty>()? {
|
||||
Token::String(property) => {
|
||||
parser.next_token::<Ignore>()?.assert(Token::Colon)?;
|
||||
filter[*pos_stack.last().unwrap()] = match &property.hash[0] {
|
||||
0x726f_7461_7265_706f => {
|
||||
match parser.next_token::<u64>()?.unwrap_string("operator")? {
|
||||
0x0044_4e41 => Filter::And,
|
||||
0x524f => Filter::Or,
|
||||
0x0054_4f4e => Filter::Not,
|
||||
_ => return Err(parser.error_value()),
|
||||
}
|
||||
}
|
||||
0x736e_6f69_7469_646e_6f63 => {
|
||||
parser.next_token::<Ignore>()?.assert(Token::ArrayStart)?;
|
||||
continue;
|
||||
}
|
||||
_ => match (&property.hash[0], &property.hash[1]) {
|
||||
(0x006c_6961_6d65, _) => {
|
||||
Filter::Email(parser.next_token::<String>()?.unwrap_string("email")?)
|
||||
}
|
||||
(0x656d_616e, _) => {
|
||||
Filter::Name(parser.next_token::<String>()?.unwrap_string("name")?)
|
||||
}
|
||||
(0x656d_614e_6e69_616d_6f64, _) => Filter::DomainName(
|
||||
parser.next_token::<String>()?.unwrap_string("domainName")?,
|
||||
),
|
||||
(0x7478_6574, _) => {
|
||||
Filter::Text(parser.next_token::<String>()?.unwrap_string("text")?)
|
||||
}
|
||||
(0x6570_7974, _) => {
|
||||
Filter::Type(parser.next_token::<String>()?.unwrap_string("type")?)
|
||||
}
|
||||
(0x656e_6f7a_656d_6974, _) => Filter::Timezone(
|
||||
parser.next_token::<String>()?.unwrap_string("timezone")?,
|
||||
),
|
||||
(0x0073_7265_626d_656d, _) => {
|
||||
Filter::Members(parser.next_token::<Id>()?.unwrap_string("members")?)
|
||||
}
|
||||
(0x6e61_6854_7265_776f_4c61_746f_7571, _) => Filter::QuotaLt(
|
||||
parser
|
||||
.next_token::<String>()?
|
||||
.unwrap_uint_or_null("quotaLowerThan")?
|
||||
.unwrap_or_default() as u32,
|
||||
),
|
||||
(0x6e61_6854_7265_7461_6572_4761_746f_7571, _) => Filter::QuotaGt(
|
||||
parser
|
||||
.next_token::<String>()?
|
||||
.unwrap_uint_or_null("quotaGreaterThan")?
|
||||
.unwrap_or_default() as u32,
|
||||
),
|
||||
(0x0073_6449_7974_6974_6e65_6469, _) => {
|
||||
Filter::IdentityIds(<Vec<Id>>::parse(parser)?)
|
||||
}
|
||||
(0x7364_496c_6961_6d65, _) => Filter::EmailIds(<Vec<Id>>::parse(parser)?),
|
||||
(0x0073_6449_6461_6572_6874, _) => {
|
||||
Filter::ThreadIds(<Vec<Id>>::parse(parser)?)
|
||||
}
|
||||
(0x7375_7461_7453_6f64_6e75, _) => Filter::UndoStatus(
|
||||
parser.next_token::<String>()?.unwrap_string("undoStatus")?,
|
||||
),
|
||||
(0x6572_6f66_6562, _) => {
|
||||
Filter::Before(parser.next_token::<UTCDate>()?.unwrap_string("before")?)
|
||||
}
|
||||
(0x0072_6574_6661, _) => {
|
||||
Filter::After(parser.next_token::<UTCDate>()?.unwrap_string("after")?)
|
||||
}
|
||||
(0x0078_6f62_6c69_614d_6e69, _) => Filter::InMailbox(
|
||||
parser.next_token::<Id>()?.unwrap_string("inMailbox")?,
|
||||
),
|
||||
(0x6854_7265_6874_4f78_6f62_6c69_614d_6e69, 0x6e61) => {
|
||||
Filter::InMailboxOtherThan(<Vec<Id>>::parse(parser)?)
|
||||
}
|
||||
(0x0065_7a69_536e_696d, _) => Filter::MinSize(
|
||||
parser
|
||||
.next_token::<String>()?
|
||||
.unwrap_uint_or_null("minSize")?
|
||||
.unwrap_or_default() as u32,
|
||||
),
|
||||
(0x0065_7a69_5378_616d, _) => Filter::MaxSize(
|
||||
parser
|
||||
.next_token::<String>()?
|
||||
.unwrap_uint_or_null("maxSize")?
|
||||
.unwrap_or_default() as u32,
|
||||
),
|
||||
(0x4b65_7661_4864_6165_7268_546e_496c_6c61, 0x6472_6f77_7965) => {
|
||||
Filter::AllInThreadHaveKeyword(
|
||||
parser
|
||||
.next_token::<Keyword>()?
|
||||
.unwrap_string("allInThreadHaveKeyword")?,
|
||||
struct FilterListCollector<'x, T: 'x>(&'x mut Vec<Filter<T>>)
|
||||
where
|
||||
T: for<'de> DeserializeArguments<'de> + Default;
|
||||
|
||||
pub(super) 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_map<V>(self, mut map: V) -> Result<(), V::Error>
|
||||
where
|
||||
V: MapAccess<'de>,
|
||||
{
|
||||
let mut filter = T::default();
|
||||
let mut has_filter = 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,
|
||||
)
|
||||
}
|
||||
(0x6576_6148_6461_6572_6854_6e49_656d_6f73, 0x0064_726f_7779_654b) => {
|
||||
Filter::SomeInThreadHaveKeyword(
|
||||
parser
|
||||
.next_token::<Keyword>()?
|
||||
.unwrap_string("someInThreadHaveKeyword")?,
|
||||
)
|
||||
}
|
||||
(0x6576_6148_6461_6572_6854_6e49_656e_6f6e, 0x0064_726f_7779_654b) => {
|
||||
Filter::NoneInThreadHaveKeyword(
|
||||
parser
|
||||
.next_token::<Keyword>()?
|
||||
.unwrap_string("noneInThreadHaveKeyword")?,
|
||||
)
|
||||
}
|
||||
(0x6472_6f77_7965_4b73_6168, _) => Filter::HasKeyword(
|
||||
parser
|
||||
.next_token::<Keyword>()?
|
||||
.unwrap_string("hasKeyword")?,
|
||||
),
|
||||
(0x6472_6f77_7965_4b74_6f6e, _) => Filter::NotKeyword(
|
||||
parser
|
||||
.next_token::<Keyword>()?
|
||||
.unwrap_string("notKeyword")?,
|
||||
),
|
||||
(0x0074_6e65_6d68_6361_7474_4173_6168, _) => Filter::HasAttachment(
|
||||
parser
|
||||
.next_token::<String>()?
|
||||
.unwrap_bool("hasAttachment")?,
|
||||
),
|
||||
(0x6d6f_7266, _) => {
|
||||
Filter::From(parser.next_token::<String>()?.unwrap_string("from")?)
|
||||
}
|
||||
(0x6f74, _) => {
|
||||
Filter::To(parser.next_token::<String>()?.unwrap_string("to")?)
|
||||
}
|
||||
(0x6363, _) => {
|
||||
Filter::Cc(parser.next_token::<String>()?.unwrap_string("cc")?)
|
||||
}
|
||||
(0x0063_6362, _) => {
|
||||
Filter::Bcc(parser.next_token::<String>()?.unwrap_string("bcc")?)
|
||||
}
|
||||
(0x0074_6365_6a62_7573, _) => Filter::Subject(
|
||||
parser.next_token::<String>()?.unwrap_string("subject")?,
|
||||
),
|
||||
(0x7964_6f62, _) => {
|
||||
Filter::Body(parser.next_token::<String>()?.unwrap_string("body")?)
|
||||
}
|
||||
(0x7265_6461_6568, _) => Filter::Header(<Vec<String>>::parse(parser)?),
|
||||
(0x6469, _) => Filter::Id(<Vec<Id>>::parse(parser)?),
|
||||
(0x6572_6f66_6542_746e_6573, _) => Filter::SentBefore(
|
||||
parser
|
||||
.next_token::<UTCDate>()?
|
||||
.unwrap_string("sentBefore")?,
|
||||
),
|
||||
(0x0072_6574_6641_746e_6573, _) => Filter::SentAfter(
|
||||
parser.next_token::<UTCDate>()?.unwrap_string("sentAfter")?,
|
||||
),
|
||||
(0x6461_6572_6854_6e69, _) => {
|
||||
Filter::InThread(parser.next_token::<Id>()?.unwrap_string("inThread")?)
|
||||
}
|
||||
(0x6449_746e_6572_6170, _) => Filter::ParentId(
|
||||
parser
|
||||
.next_token::<Id>()?
|
||||
.unwrap_string_or_null("parentId")?,
|
||||
),
|
||||
(0x656c_6f72, _) => Filter::Role(
|
||||
parser
|
||||
.next_token::<String>()?
|
||||
.unwrap_string_or_null("role")?,
|
||||
),
|
||||
(0x656c_6f52_796e_4173_6168, _) => Filter::HasAnyRole(
|
||||
parser.next_token::<String>()?.unwrap_bool("hasAnyRole")?,
|
||||
),
|
||||
(0x6465_6269_7263_7362_7553_7369, _) => Filter::IsSubscribed(
|
||||
parser.next_token::<String>()?.unwrap_bool("isSubscribed")?,
|
||||
),
|
||||
(0x6576_6974_6341_7369, _) => Filter::IsActive(
|
||||
parser.next_token::<String>()?.unwrap_bool("isActive")?,
|
||||
),
|
||||
(0x0065_706f_6373, _) => {
|
||||
Filter::Scope(parser.next_token::<String>()?.unwrap_string("scope")?)
|
||||
}
|
||||
(0x6570_7954_6563_7275_6f73_6572, _) => Filter::ResourceType(
|
||||
parser
|
||||
.next_token::<String>()?
|
||||
.unwrap_string("resourceType")?,
|
||||
),
|
||||
_ => {
|
||||
if parser.is_eof || parser.skip_string() {
|
||||
let filter = Filter::_T(
|
||||
String::from_utf8_lossy(
|
||||
parser.bytes[parser.pos_marker..parser.pos - 1].as_ref(),
|
||||
)
|
||||
.into_owned(),
|
||||
);
|
||||
parser.skip_token(parser.depth_array, parser.depth_dict)?;
|
||||
filter
|
||||
.ok_or_else(|| {
|
||||
de::Error::custom(format!("Unknown filter operator: {}", key))
|
||||
})?;
|
||||
|
||||
if let Some(pos) = has_conditions {
|
||||
self.0[pos] = op_;
|
||||
} else {
|
||||
return Err(parser.error_unterminated());
|
||||
op = Some(op_);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
Token::DictStart => {
|
||||
pos_stack.push(filter.len());
|
||||
filter.push(Filter::Close);
|
||||
}
|
||||
Token::DictEnd => {
|
||||
if !matches!(filter[pos_stack.pop().unwrap()], Filter::Close) {
|
||||
if pos_stack.is_empty() {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
return Err(trc::JmapEvent::InvalidArguments
|
||||
.into_err()
|
||||
.details("Malformed filter"));
|
||||
}
|
||||
}
|
||||
Token::ArrayEnd => {
|
||||
filter.push(Filter::Close);
|
||||
}
|
||||
Token::Comma => (),
|
||||
token => {
|
||||
return Err(token.error("filter", "object or array"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(filter)
|
||||
}
|
||||
|
||||
pub fn parse_sort(parser: &mut Parser) -> trc::Result<Vec<Comparator>> {
|
||||
let mut sort = vec![];
|
||||
|
||||
loop {
|
||||
match parser.next_token::<String>()? {
|
||||
Token::DictStart => {
|
||||
let mut comp = Comparator {
|
||||
is_ascending: true,
|
||||
collation: None,
|
||||
property: SortProperty::Type,
|
||||
keyword: None,
|
||||
};
|
||||
while let Some(key) = parser.next_dict_key::<u128>()? {
|
||||
match key {
|
||||
0x0067_6e69_646e_6563_7341_7369 => {
|
||||
comp.is_ascending = parser
|
||||
.next_token::<Ignore>()?
|
||||
.unwrap_bool_or_null("isAscending")?
|
||||
.unwrap_or_default();
|
||||
}
|
||||
0x006e_6f69_7461_6c6c_6f63 => {
|
||||
comp.collation = parser
|
||||
.next_token::<String>()?
|
||||
.unwrap_string_or_null("collation")?;
|
||||
}
|
||||
0x7974_7265_706f_7270 => {
|
||||
comp.property = parser
|
||||
.next_token::<SortProperty>()?
|
||||
.unwrap_string("property")?;
|
||||
}
|
||||
0x0064_726f_7779_656b => {
|
||||
comp.keyword = parser
|
||||
.next_token::<Keyword>()?
|
||||
.unwrap_string_or_null("keyword")?;
|
||||
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);
|
||||
}
|
||||
_ => {
|
||||
parser.skip_token(parser.depth_array, parser.depth_dict)?;
|
||||
filter.deserialize_argument(&key, &mut map)?;
|
||||
has_filter = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.push(comp);
|
||||
}
|
||||
Token::Comma => (),
|
||||
Token::ArrayEnd => {
|
||||
break;
|
||||
}
|
||||
token => {
|
||||
return Err(token.error("sort", "object"));
|
||||
|
||||
if has_filter {
|
||||
if has_conditions.is_some() {
|
||||
return Err(de::Error::custom(
|
||||
"Cannot mix conditions with property filters",
|
||||
));
|
||||
}
|
||||
|
||||
self.0.push(Filter::Property(filter));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(sort)
|
||||
deserializer.deserialize_map(FilterVisitor(self.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl JsonObjectParser for SortProperty {
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
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
|
||||
Self: Sized,
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let mut hash = 0;
|
||||
let mut shift = 0;
|
||||
struct FilterVisitor<'x, T: 'x>(&'x mut Vec<Filter<T>>)
|
||||
where
|
||||
T: for<'de2> DeserializeArguments<'de2> + Default;
|
||||
|
||||
while let Some(ch) = parser.next_unescaped()? {
|
||||
if ch.is_ascii_alphabetic() {
|
||||
if shift < 128 {
|
||||
hash |= (ch as u128) << shift;
|
||||
shift += 8;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
hash = 0;
|
||||
break;
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
match hash {
|
||||
0x6570_7974 => Ok(SortProperty::Type),
|
||||
0x656d_616e => Ok(SortProperty::Name),
|
||||
0x006c_6961_6d65 => Ok(SortProperty::Email),
|
||||
0x0064_496c_6961_6d65 => Ok(SortProperty::EmailId),
|
||||
0x6449_6461_6572_6874 => Ok(SortProperty::ThreadId),
|
||||
0x7441_746e_6573 => Ok(SortProperty::SentAt),
|
||||
0x7441_6465_7669_6563_6572 => Ok(SortProperty::ReceivedAt),
|
||||
0x657a_6973 => Ok(SortProperty::Size),
|
||||
0x6d6f_7266 => Ok(SortProperty::From),
|
||||
0x6f74 => Ok(SortProperty::To),
|
||||
0x0074_6365_6a62_7573 => Ok(SortProperty::Subject),
|
||||
0x6363 => Ok(SortProperty::Cc),
|
||||
0x0072_6564_724f_7472_6f73 => Ok(SortProperty::SortOrder),
|
||||
0x6449_746e_6572_6170 => Ok(SortProperty::ParentId),
|
||||
0x6576_6974_6341_7369 => Ok(SortProperty::IsActive),
|
||||
0x6472_6f77_7965_4b73_6168 => Ok(SortProperty::HasKeyword),
|
||||
0x4b65_7661_4864_6165_7268_546e_496c_6c61 => Ok(SortProperty::AllInThreadHaveKeyword),
|
||||
0x6576_6148_6461_6572_6854_6e49_656d_6f73 => Ok(SortProperty::SomeInThreadHaveKeyword),
|
||||
0x6465_7375 => Ok(SortProperty::Used),
|
||||
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()?;
|
||||
},
|
||||
_ => {
|
||||
if parser.is_eof || parser.skip_string() {
|
||||
Ok(SortProperty::_T(
|
||||
String::from_utf8_lossy(
|
||||
parser.bytes[parser.pos_marker..parser.pos - 1].as_ref(),
|
||||
)
|
||||
.into_owned(),
|
||||
))
|
||||
} else {
|
||||
Err(parser.error_unterminated())
|
||||
}
|
||||
self.property.deserialize_argument(key, map)?;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
impl Display for Filter {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(match self {
|
||||
Filter::Email(_) => "email",
|
||||
Filter::Name(_) => "name",
|
||||
Filter::DomainName(_) => "domainName",
|
||||
Filter::Text(_) => "text",
|
||||
Filter::Type(_) => "type",
|
||||
Filter::Timezone(_) => "timezone",
|
||||
Filter::Members(_) => "members",
|
||||
Filter::QuotaLt(_) => "quotaLt",
|
||||
Filter::QuotaGt(_) => "quotaGt",
|
||||
Filter::IdentityIds(_) => "identityIds",
|
||||
Filter::EmailIds(_) => "emailIds",
|
||||
Filter::ThreadIds(_) => "threadIds",
|
||||
Filter::UndoStatus(_) => "undoStatus",
|
||||
Filter::Before(_) => "before",
|
||||
Filter::After(_) => "after",
|
||||
Filter::InMailbox(_) => "inMailbox",
|
||||
Filter::InMailboxOtherThan(_) => "inMailboxOtherThan",
|
||||
Filter::MinSize(_) => "minSize",
|
||||
Filter::MaxSize(_) => "maxSize",
|
||||
Filter::AllInThreadHaveKeyword(_) => "allInThreadHaveKeyword",
|
||||
Filter::SomeInThreadHaveKeyword(_) => "someInThreadHaveKeyword",
|
||||
Filter::NoneInThreadHaveKeyword(_) => "noneInThreadHaveKeyword",
|
||||
Filter::HasKeyword(_) => "hasKeyword",
|
||||
Filter::NotKeyword(_) => "notKeyword",
|
||||
Filter::HasAttachment(_) => "hasAttachment",
|
||||
Filter::From(_) => "from",
|
||||
Filter::To(_) => "to",
|
||||
Filter::Cc(_) => "cc",
|
||||
Filter::Bcc(_) => "bcc",
|
||||
Filter::Subject(_) => "subject",
|
||||
Filter::Body(_) => "body",
|
||||
Filter::Header(_) => "header",
|
||||
Filter::Id(_) => "id",
|
||||
Filter::SentBefore(_) => "sentBefore",
|
||||
Filter::SentAfter(_) => "sentAfter",
|
||||
Filter::InThread(_) => "inThread",
|
||||
Filter::ParentId(_) => "parentId",
|
||||
Filter::Role(_) => "role",
|
||||
Filter::HasAnyRole(_) => "hasAnyRole",
|
||||
Filter::IsSubscribed(_) => "isSubscribed",
|
||||
Filter::IsActive(_) => "isActive",
|
||||
Filter::ResourceType(_) => "resourceType",
|
||||
Filter::Scope(_) => "scope",
|
||||
Filter::_T(v) => v.as_str(),
|
||||
Filter::And => "and",
|
||||
Filter::Or => "or",
|
||||
Filter::Not => "not",
|
||||
Filter::Close => "close",
|
||||
})
|
||||
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 Display for SortProperty {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(match self {
|
||||
SortProperty::Type => "type",
|
||||
SortProperty::Name => "name",
|
||||
SortProperty::Email => "email",
|
||||
SortProperty::EmailId => "emailId",
|
||||
SortProperty::ThreadId => "threadId",
|
||||
SortProperty::SentAt => "sentAt",
|
||||
SortProperty::ReceivedAt => "receivedAt",
|
||||
SortProperty::Size => "size",
|
||||
SortProperty::From => "from",
|
||||
SortProperty::To => "to",
|
||||
SortProperty::Subject => "subject",
|
||||
SortProperty::Cc => "cc",
|
||||
SortProperty::SortOrder => "sortOrder",
|
||||
SortProperty::ParentId => "parentId",
|
||||
SortProperty::IsActive => "isActive",
|
||||
SortProperty::HasKeyword => "hasKeyword",
|
||||
SortProperty::AllInThreadHaveKeyword => "allInThreadHaveKeyword",
|
||||
SortProperty::SomeInThreadHaveKeyword => "someInThreadHaveKeyword",
|
||||
SortProperty::Used => "used",
|
||||
SortProperty::_T(s) => s,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Filter {
|
||||
pub fn is_immutable(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Filter::Before(_)
|
||||
| Filter::After(_)
|
||||
| Filter::MinSize(_)
|
||||
| Filter::MaxSize(_)
|
||||
| Filter::Text(_)
|
||||
| Filter::HasAttachment(_)
|
||||
| Filter::From(_)
|
||||
| Filter::To(_)
|
||||
| Filter::Cc(_)
|
||||
| Filter::Bcc(_)
|
||||
| Filter::Subject(_)
|
||||
| Filter::Body(_)
|
||||
| Filter::Header(_)
|
||||
| Filter::Id(_)
|
||||
| Filter::SentBefore(_)
|
||||
| Filter::SentAfter(_)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Comparator {
|
||||
pub fn descending(property: SortProperty) -> Self {
|
||||
Self {
|
||||
property,
|
||||
is_ascending: false,
|
||||
collation: None,
|
||||
keyword: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ascending(property: SortProperty) -> Self {
|
||||
Self {
|
||||
property,
|
||||
is_ascending: true,
|
||||
collation: None,
|
||||
keyword: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_immutable(&self) -> bool {
|
||||
matches!(
|
||||
&self.property,
|
||||
SortProperty::SentAt
|
||||
| SortProperty::ReceivedAt
|
||||
| SortProperty::Size
|
||||
| SortProperty::From
|
||||
| SortProperty::To
|
||||
| SortProperty::Subject
|
||||
| SortProperty::Cc
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Filter> for store::query::Filter {
|
||||
fn from(value: Filter) -> Self {
|
||||
impl<T> From<Filter<T>> for store::query::Filter
|
||||
where
|
||||
T: for<'de> DeserializeArguments<'de> + Default,
|
||||
{
|
||||
fn from(value: Filter<T>) -> Self {
|
||||
match value {
|
||||
Filter::And => Self::And,
|
||||
Filter::Or => Self::Or,
|
||||
@@ -707,8 +343,11 @@ impl From<Filter> for store::query::Filter {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Into<u8> + Display + Clone + std::fmt::Debug> From<Filter> for FtsFilter<T> {
|
||||
fn from(value: Filter) -> Self {
|
||||
impl<T: Into<u8> + Display + Clone + std::fmt::Debug, U> From<Filter<U>> for FtsFilter<T>
|
||||
where
|
||||
U: for<'de> DeserializeArguments<'de> + Default,
|
||||
{
|
||||
fn from(value: Filter<U>) -> Self {
|
||||
match value {
|
||||
Filter::And => Self::And,
|
||||
Filter::Or => Self::Or,
|
||||
@@ -719,27 +358,10 @@ impl<T: Into<u8> + Display + Clone + std::fmt::Debug> From<Filter> for FtsFilter
|
||||
}
|
||||
}
|
||||
|
||||
impl FilterItem for Filter {
|
||||
fn filter_type(&self) -> FilterType {
|
||||
match self {
|
||||
Filter::Text(_)
|
||||
| Filter::From(_)
|
||||
| Filter::To(_)
|
||||
| Filter::Cc(_)
|
||||
| Filter::Bcc(_)
|
||||
| Filter::Subject(_)
|
||||
| Filter::Body(_)
|
||||
| Filter::Header(_) => FilterType::Fts,
|
||||
Filter::And => FilterType::And,
|
||||
Filter::Or => FilterType::Or,
|
||||
Filter::Not => FilterType::Not,
|
||||
Filter::Close => FilterType::End,
|
||||
_ => FilterType::Store,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FilterType> for Filter {
|
||||
impl<T> From<FilterType> for Filter<T>
|
||||
where
|
||||
T: for<'de> DeserializeArguments<'de> + Default,
|
||||
{
|
||||
fn from(value: FilterType) -> Self {
|
||||
match value {
|
||||
FilterType::And => Filter::And,
|
||||
@@ -751,4 +373,53 @@ impl From<FilterType> for Filter {
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
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> FilterItem for Filter<T>
|
||||
where
|
||||
T: for<'de> DeserializeArguments<'de> + FilterItem + Default,
|
||||
{
|
||||
fn filter_type(&self) -> FilterType {
|
||||
match self {
|
||||
Filter::And => FilterType::And,
|
||||
Filter::Or => FilterType::Or,
|
||||
Filter::Not => FilterType::Not,
|
||||
Filter::Close => FilterType::End,
|
||||
Filter::Property(p) => p.filter_type(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Display for Filter<T>
|
||||
where
|
||||
T: for<'de> DeserializeArguments<'de> + Display + Default,
|
||||
{
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Filter::And => write!(f, "and"),
|
||||
Filter::Or => write!(f, "or"),
|
||||
Filter::Not => write!(f, "not"),
|
||||
Filter::Close => write!(f, "close"),
|
||||
Filter::Property(p) => write!(f, "{}", p),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
method::query::{Comparator, Filter, FilterWrapper},
|
||||
object::JmapObject,
|
||||
request::deserialize::{DeserializeArguments, deserialize_request},
|
||||
types::state::State,
|
||||
@@ -15,8 +16,8 @@ use types::id::Id;
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct QueryChangesRequest<T: JmapObject> {
|
||||
pub account_id: Id,
|
||||
pub filter: Vec<T::Filter>,
|
||||
pub sort: Option<Vec<T::Comparator>>,
|
||||
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>,
|
||||
@@ -68,7 +69,7 @@ impl<'de, T: JmapObject> DeserializeArguments<'de> for QueryChangesRequest<T> {
|
||||
self.account_id = map.next_value()?;
|
||||
},
|
||||
b"filter" => {
|
||||
self.filter = map.next_value()?;
|
||||
self.filter = map.next_value::<FilterWrapper<T::Filter>>()?.0;
|
||||
},
|
||||
b"sort" => {
|
||||
self.sort = map.next_value()?;
|
||||
|
||||
@@ -5,18 +5,22 @@
|
||||
*/
|
||||
|
||||
use super::query::Filter;
|
||||
use crate::request::{
|
||||
MaybeInvalid,
|
||||
deserialize::{DeserializeArguments, deserialize_request},
|
||||
reference::{MaybeResultReference, ResultReference},
|
||||
use crate::{
|
||||
method::query::FilterWrapper,
|
||||
object::email::EmailFilter,
|
||||
request::{
|
||||
MaybeInvalid,
|
||||
deserialize::{DeserializeArguments, deserialize_request},
|
||||
reference::{MaybeResultReference, ResultReference},
|
||||
},
|
||||
};
|
||||
use serde::{Deserialize, Deserializer, de::DeserializeOwned};
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use types::id::Id;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GetSearchSnippetRequest<T> {
|
||||
pub struct GetSearchSnippetRequest {
|
||||
pub account_id: Id,
|
||||
pub filter: Vec<Filter<T>>,
|
||||
pub filter: Vec<Filter<EmailFilter>>,
|
||||
pub email_ids: MaybeResultReference<Vec<MaybeInvalid<Id>>>,
|
||||
}
|
||||
|
||||
@@ -45,7 +49,7 @@ pub struct SearchSnippet {
|
||||
pub preview: Option<String>,
|
||||
}
|
||||
|
||||
impl<'de, T: DeserializeOwned> DeserializeArguments<'de> for GetSearchSnippetRequest<T> {
|
||||
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>,
|
||||
@@ -55,7 +59,7 @@ impl<'de, T: DeserializeOwned> DeserializeArguments<'de> for GetSearchSnippetReq
|
||||
self.account_id = map.next_value()?;
|
||||
},
|
||||
b"filter" => {
|
||||
self.filter = map.next_value()?;
|
||||
self.filter = map.next_value::<FilterWrapper<EmailFilter>>()?.0;
|
||||
},
|
||||
b"emailIds" => {
|
||||
self.email_ids = MaybeResultReference::Value(map.next_value::<Vec<MaybeInvalid<Id>>>()?);
|
||||
@@ -72,7 +76,7 @@ impl<'de, T: DeserializeOwned> DeserializeArguments<'de> for GetSearchSnippetReq
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, T: DeserializeOwned> Deserialize<'de> for GetSearchSnippetRequest<T> {
|
||||
impl<'de> Deserialize<'de> for GetSearchSnippetRequest {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
@@ -81,7 +85,7 @@ impl<'de, T: DeserializeOwned> Deserialize<'de> for GetSearchSnippetRequest<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for GetSearchSnippetRequest<T> {
|
||||
impl Default for GetSearchSnippetRequest {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
account_id: Id::default(),
|
||||
|
||||
@@ -11,18 +11,16 @@ use crate::{
|
||||
request::{
|
||||
MaybeInvalid,
|
||||
deserialize::{DeserializeArguments, deserialize_request},
|
||||
method::MethodObject,
|
||||
reference::{MaybeResultReference, ResultReference},
|
||||
},
|
||||
response::Response,
|
||||
types::{date::UTCDate, state::State},
|
||||
types::state::State,
|
||||
};
|
||||
use ahash::AHashMap;
|
||||
use compact_str::format_compact;
|
||||
use jmap_tools::Value;
|
||||
use jmap_tools::{Key, Map, Value};
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use types::{acl::Acl, blob::BlobId, id::Id, keyword::Keyword};
|
||||
use utils::map::{bitmap::Bitmap, vec_map::VecMap};
|
||||
use types::id::Id;
|
||||
use utils::map::vec_map::VecMap;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(clippy::type_complexity)]
|
||||
@@ -36,6 +34,7 @@ pub struct SetRequest<'x, T: JmapObject> {
|
||||
}
|
||||
|
||||
#[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")]
|
||||
@@ -166,15 +165,14 @@ impl<'x, T: JmapObject> SetRequest<'x, T> {
|
||||
self.update.take().unwrap_or_default()
|
||||
}
|
||||
|
||||
/*pub fn unwrap_destroy(&mut self) -> Vec<Id> {
|
||||
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());
|
||||
@@ -213,14 +211,21 @@ impl<T: JmapObject> SetResponse<T> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn created(&mut self, id: String, document_id: u32) {
|
||||
pub fn created(&mut self, id: String, document_id: impl Into<T::Id>) {
|
||||
self.created.insert(
|
||||
id,
|
||||
Object::with_capacity(1).with_property(Property::Id, Value::Id(document_id.into())),
|
||||
Value::Object(Map::from(vec![(
|
||||
Key::Property(T::ID_PROPERTY),
|
||||
Value::Element(T::Element::from(document_id.into())),
|
||||
)])),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn invalid_property_create(&mut self, id: String, property: impl Into<InvalidProperty>) {
|
||||
pub fn invalid_property_create(
|
||||
&mut self,
|
||||
id: String,
|
||||
property: impl Into<InvalidProperty<T::Property>>,
|
||||
) {
|
||||
self.not_created.append(
|
||||
id,
|
||||
SetError::invalid_properties()
|
||||
@@ -229,7 +234,11 @@ impl<T: JmapObject> SetResponse<T> {
|
||||
);
|
||||
}
|
||||
|
||||
pub fn invalid_property_update(&mut self, id: Id, property: impl Into<InvalidProperty>) {
|
||||
pub fn invalid_property_update(
|
||||
&mut self,
|
||||
id: MaybeInvalid<Id>,
|
||||
property: impl Into<InvalidProperty<T::Property>>,
|
||||
) {
|
||||
self.not_updated.append(
|
||||
id,
|
||||
SetError::invalid_properties()
|
||||
@@ -240,30 +249,15 @@ impl<T: JmapObject> SetResponse<T> {
|
||||
|
||||
pub fn update_created_ids(&self, response: &mut Response) {
|
||||
for (user_id, obj) in &self.created {
|
||||
if let Some(id) = obj.get(&Property::Id).as_id() {
|
||||
response.created_ids.insert(user_id.clone(), (*id).into());
|
||||
if let Value::Object(obj) = obj
|
||||
&& let Some(id) = obj.get(&Key::Property(T::ID_PROPERTY))
|
||||
{
|
||||
response.created_ids.insert(user_id.clone(), id.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_object_by_id(&mut self, id: Id) -> Option<&mut Value<'x, P, E>> {
|
||||
if let Some(obj) = self.updated.get_mut(&id) {
|
||||
if let Some(obj) = obj {
|
||||
return Some(obj);
|
||||
} else {
|
||||
*obj = Some(Object::with_capacity(1));
|
||||
return obj.as_mut().unwrap().into();
|
||||
}
|
||||
}
|
||||
|
||||
(&mut self.created)
|
||||
.into_iter()
|
||||
.map(|(_, obj)| obj)
|
||||
.find(|obj| obj.0.get(&Property::Id) == Some(&Value::Id(id)))
|
||||
}
|
||||
|
||||
pub fn has_changes(&self) -> bool {
|
||||
!self.created.is_empty() || !self.updated.is_empty() || !self.destroyed.is_empty()
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -174,15 +174,15 @@ impl<'de> DeserializeArguments<'de> for DataSourceObject {
|
||||
}
|
||||
}
|
||||
|
||||
/*impl BlobUploadResponse {
|
||||
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(), obj.id.clone().into());
|
||||
.insert(user_id.clone(), obj.id.to_string());
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for DataSourceObject {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
|
||||
@@ -182,4 +182,12 @@ impl JmapObject for Blob {
|
||||
type QueryArguments = ();
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = BlobProperty::Id;
|
||||
}
|
||||
|
||||
impl From<BlobId> for BlobValue {
|
||||
fn from(id: BlobId) -> Self {
|
||||
BlobValue::BlobId(id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ use crate::{
|
||||
use jmap_tools::{Element, JsonPointer, JsonPointerItem, Key, Property};
|
||||
use mail_parser::HeaderName;
|
||||
use std::{borrow::Cow, fmt::Display, str::FromStr};
|
||||
use store::fts::{FilterItem, FilterType};
|
||||
use types::{blob::BlobId, id::Id, keyword::Keyword};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
@@ -318,7 +319,7 @@ impl HeaderProperty {
|
||||
}
|
||||
);
|
||||
}
|
||||
2 if value == "all" && result.all == false => {
|
||||
2 if value == "all" && !result.all => {
|
||||
result.all = true;
|
||||
}
|
||||
_ => return None,
|
||||
@@ -438,9 +439,9 @@ impl JmapObject for Email {
|
||||
|
||||
type Id = Id;
|
||||
|
||||
type Filter = ();
|
||||
type Filter = EmailFilter;
|
||||
|
||||
type Comparator = ();
|
||||
type Comparator = EmailComparator;
|
||||
|
||||
type GetArguments = EmailGetArguments;
|
||||
|
||||
@@ -449,4 +450,345 @@ impl JmapObject for Email {
|
||||
type QueryArguments = ();
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = EmailProperty::Id;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum EmailFilter {
|
||||
InMailbox(Id),
|
||||
InMailboxOtherThan(Vec<Id>),
|
||||
Before(UTCDate),
|
||||
After(UTCDate),
|
||||
MinSize(u32),
|
||||
MaxSize(u32),
|
||||
AllInThreadHaveKeyword(Keyword),
|
||||
SomeInThreadHaveKeyword(Keyword),
|
||||
NoneInThreadHaveKeyword(Keyword),
|
||||
HasKeyword(Keyword),
|
||||
NotKeyword(Keyword),
|
||||
HasAttachment(bool),
|
||||
From(String),
|
||||
To(String),
|
||||
Cc(String),
|
||||
Bcc(String),
|
||||
Subject(String),
|
||||
Body(String),
|
||||
Header(Vec<String>),
|
||||
Text(String),
|
||||
SentBefore(UTCDate),
|
||||
SentAfter(UTCDate),
|
||||
InThread(Id),
|
||||
Id(Vec<Id>),
|
||||
_T(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum EmailComparator {
|
||||
ReceivedAt,
|
||||
Size,
|
||||
From,
|
||||
To,
|
||||
Subject,
|
||||
Cc,
|
||||
SentAt,
|
||||
ThreadId,
|
||||
HasKeyword(Keyword),
|
||||
AllInThreadHaveKeyword(Keyword),
|
||||
SomeInThreadHaveKeyword(Keyword),
|
||||
_T(String),
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for EmailFilter {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
hashify::fnc_map!(key.as_bytes(),
|
||||
b"inMailbox" => {
|
||||
*self = EmailFilter::InMailbox(map.next_value()?);
|
||||
},
|
||||
b"inMailboxOtherThan" => {
|
||||
*self = EmailFilter::InMailboxOtherThan(map.next_value()?);
|
||||
},
|
||||
b"before" => {
|
||||
*self = EmailFilter::Before(map.next_value()?);
|
||||
},
|
||||
b"after" => {
|
||||
*self = EmailFilter::After(map.next_value()?);
|
||||
},
|
||||
b"minSize" => {
|
||||
*self = EmailFilter::MinSize(map.next_value()?);
|
||||
},
|
||||
b"maxSize" => {
|
||||
*self = EmailFilter::MaxSize(map.next_value()?);
|
||||
},
|
||||
b"allInThreadHaveKeyword" => {
|
||||
*self = EmailFilter::AllInThreadHaveKeyword(map.next_value()?);
|
||||
},
|
||||
b"someInThreadHaveKeyword" => {
|
||||
*self = EmailFilter::SomeInThreadHaveKeyword(map.next_value()?);
|
||||
},
|
||||
b"noneInThreadHaveKeyword" => {
|
||||
*self = EmailFilter::NoneInThreadHaveKeyword(map.next_value()?);
|
||||
},
|
||||
b"hasKeyword" => {
|
||||
*self = EmailFilter::HasKeyword(map.next_value()?);
|
||||
},
|
||||
b"notKeyword" => {
|
||||
*self = EmailFilter::NotKeyword(map.next_value()?);
|
||||
},
|
||||
b"hasAttachment" => {
|
||||
*self = EmailFilter::HasAttachment(map.next_value()?);
|
||||
},
|
||||
b"from" => {
|
||||
*self = EmailFilter::From(map.next_value()?);
|
||||
},
|
||||
b"to" => {
|
||||
*self = EmailFilter::To(map.next_value()?);
|
||||
},
|
||||
b"cc" => {
|
||||
*self = EmailFilter::Cc(map.next_value()?);
|
||||
},
|
||||
b"bcc" => {
|
||||
*self = EmailFilter::Bcc(map.next_value()?);
|
||||
},
|
||||
b"subject" => {
|
||||
*self = EmailFilter::Subject(map.next_value()?);
|
||||
},
|
||||
b"body" => {
|
||||
*self = EmailFilter::Body(map.next_value()?);
|
||||
},
|
||||
b"header" => {
|
||||
*self = EmailFilter::Header(map.next_value()?);
|
||||
},
|
||||
b"text" => {
|
||||
*self = EmailFilter::Text(map.next_value()?);
|
||||
},
|
||||
b"sentBefore" => {
|
||||
*self = EmailFilter::SentBefore(map.next_value()?);
|
||||
},
|
||||
b"sentAfter" => {
|
||||
*self = EmailFilter::SentAfter(map.next_value()?);
|
||||
},
|
||||
b"inThread" => {
|
||||
*self = EmailFilter::InThread(map.next_value()?);
|
||||
},
|
||||
b"id" => {
|
||||
*self = EmailFilter::Id(map.next_value()?);
|
||||
},
|
||||
_ => {
|
||||
*self = EmailFilter::_T(key.to_string());
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for EmailComparator {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
if key == "property" {
|
||||
let value = map.next_value::<Cow<str>>()?;
|
||||
hashify::fnc_map!(value.as_bytes(),
|
||||
b"receivedAt" => {
|
||||
*self = EmailComparator::ReceivedAt;
|
||||
},
|
||||
b"size" => {
|
||||
*self = EmailComparator::Size;
|
||||
},
|
||||
b"from" => {
|
||||
*self = EmailComparator::From;
|
||||
},
|
||||
b"to" => {
|
||||
*self = EmailComparator::To;
|
||||
},
|
||||
b"cc" => {
|
||||
*self = EmailComparator::Cc;
|
||||
},
|
||||
b"subject" => {
|
||||
*self = EmailComparator::Subject;
|
||||
},
|
||||
b"sentAt" => {
|
||||
*self = EmailComparator::SentAt;
|
||||
},
|
||||
b"threadId" => {
|
||||
*self = EmailComparator::ThreadId;
|
||||
},
|
||||
b"hasKeyword" => {
|
||||
*self = EmailComparator::HasKeyword(self.take_keyword());
|
||||
},
|
||||
b"allInThreadHaveKeyword" => {
|
||||
*self = EmailComparator::AllInThreadHaveKeyword(self.take_keyword());
|
||||
},
|
||||
b"someInThreadHaveKeyword" => {
|
||||
*self = EmailComparator::SomeInThreadHaveKeyword(self.take_keyword());
|
||||
},
|
||||
_ => {
|
||||
*self = EmailComparator::_T(key.to_string());
|
||||
}
|
||||
);
|
||||
} else if key == "keyword" {
|
||||
let keyword: Keyword = map.next_value()?;
|
||||
match self {
|
||||
EmailComparator::HasKeyword(_) => *self = EmailComparator::HasKeyword(keyword),
|
||||
EmailComparator::AllInThreadHaveKeyword(_) => {
|
||||
*self = EmailComparator::AllInThreadHaveKeyword(keyword)
|
||||
}
|
||||
EmailComparator::SomeInThreadHaveKeyword(_) => {
|
||||
*self = EmailComparator::SomeInThreadHaveKeyword(keyword)
|
||||
}
|
||||
_ => {
|
||||
*self = EmailComparator::HasKeyword(keyword);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EmailFilter {
|
||||
fn default() -> Self {
|
||||
EmailFilter::_T("".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EmailComparator {
|
||||
fn default() -> Self {
|
||||
EmailComparator::_T("".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl EmailComparator {
|
||||
fn take_keyword(&mut self) -> Keyword {
|
||||
match self {
|
||||
EmailComparator::HasKeyword(k) => std::mem::replace(k, Keyword::Other(String::new())),
|
||||
EmailComparator::AllInThreadHaveKeyword(k) => {
|
||||
std::mem::replace(k, Keyword::Other(String::new()))
|
||||
}
|
||||
EmailComparator::SomeInThreadHaveKeyword(k) => {
|
||||
std::mem::replace(k, Keyword::Other(String::new()))
|
||||
}
|
||||
_ => Keyword::Other(String::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for EmailFilter {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(match self {
|
||||
EmailFilter::InMailbox(_) => "inMailbox",
|
||||
EmailFilter::InMailboxOtherThan(_) => "inMailboxOtherThan",
|
||||
EmailFilter::Before(_) => "before",
|
||||
EmailFilter::After(_) => "after",
|
||||
EmailFilter::MinSize(_) => "minSize",
|
||||
EmailFilter::MaxSize(_) => "maxSize",
|
||||
EmailFilter::AllInThreadHaveKeyword(_) => "allInThreadHaveKeyword",
|
||||
EmailFilter::SomeInThreadHaveKeyword(_) => "someInThreadHaveKeyword",
|
||||
EmailFilter::NoneInThreadHaveKeyword(_) => "noneInThreadHaveKeyword",
|
||||
EmailFilter::HasKeyword(_) => "hasKeyword",
|
||||
EmailFilter::NotKeyword(_) => "notKeyword",
|
||||
EmailFilter::HasAttachment(_) => "hasAttachment",
|
||||
EmailFilter::From(_) => "from",
|
||||
EmailFilter::To(_) => "to",
|
||||
EmailFilter::Cc(_) => "cc",
|
||||
EmailFilter::Bcc(_) => "bcc",
|
||||
EmailFilter::Subject(_) => "subject",
|
||||
EmailFilter::Body(_) => "body",
|
||||
EmailFilter::Header(_) => "header",
|
||||
EmailFilter::Text(_) => "text",
|
||||
EmailFilter::SentBefore(_) => "sentBefore",
|
||||
EmailFilter::SentAfter(_) => "sentAfter",
|
||||
EmailFilter::InThread(_) => "inThread",
|
||||
EmailFilter::Id(_) => "id",
|
||||
EmailFilter::_T(v) => v.as_str(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for EmailComparator {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(match self {
|
||||
EmailComparator::ReceivedAt => "receivedAt",
|
||||
EmailComparator::Size => "size",
|
||||
EmailComparator::From => "from",
|
||||
EmailComparator::To => "to",
|
||||
EmailComparator::Subject => "subject",
|
||||
EmailComparator::Cc => "cc",
|
||||
EmailComparator::SentAt => "sentAt",
|
||||
EmailComparator::ThreadId => "threadId",
|
||||
EmailComparator::HasKeyword(_) => "hasKeyword",
|
||||
EmailComparator::AllInThreadHaveKeyword(_) => "allInThreadHaveKeyword",
|
||||
EmailComparator::SomeInThreadHaveKeyword(_) => "someInThreadHaveKeyword",
|
||||
EmailComparator::_T(v) => v.as_str(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl EmailFilter {
|
||||
pub fn is_immutable(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
EmailFilter::Before(_)
|
||||
| EmailFilter::After(_)
|
||||
| EmailFilter::MinSize(_)
|
||||
| EmailFilter::MaxSize(_)
|
||||
| EmailFilter::HasAttachment(_)
|
||||
| EmailFilter::From(_)
|
||||
| EmailFilter::To(_)
|
||||
| EmailFilter::Cc(_)
|
||||
| EmailFilter::Bcc(_)
|
||||
| EmailFilter::Subject(_)
|
||||
| EmailFilter::Body(_)
|
||||
| EmailFilter::Header(_)
|
||||
| EmailFilter::Text(_)
|
||||
| EmailFilter::Id(_)
|
||||
| EmailFilter::SentBefore(_)
|
||||
| EmailFilter::SentAfter(_)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl EmailComparator {
|
||||
pub fn is_immutable(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
EmailComparator::ReceivedAt
|
||||
| EmailComparator::Size
|
||||
| EmailComparator::From
|
||||
| EmailComparator::To
|
||||
| EmailComparator::Subject
|
||||
| EmailComparator::Cc
|
||||
| EmailComparator::SentAt
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FilterItem for EmailFilter {
|
||||
fn filter_type(&self) -> FilterType {
|
||||
match self {
|
||||
EmailFilter::From(_)
|
||||
| EmailFilter::To(_)
|
||||
| EmailFilter::Cc(_)
|
||||
| EmailFilter::Bcc(_)
|
||||
| EmailFilter::Subject(_)
|
||||
| EmailFilter::Body(_)
|
||||
| EmailFilter::Header(_)
|
||||
| EmailFilter::Text(_) => FilterType::Fts,
|
||||
_ => FilterType::Store,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Id> for EmailValue {
|
||||
fn from(id: Id) -> Self {
|
||||
EmailValue::Id(id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,6 +297,16 @@ impl FromStr for EmailSubmissionProperty {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for UndoStatus {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
UndoStatus::parse(<&str>::deserialize(deserializer)?)
|
||||
.ok_or_else(|| serde::de::Error::custom("invalid JMAP UndoStatus"))
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObject for EmailSubmission {
|
||||
type Property = EmailSubmissionProperty;
|
||||
|
||||
@@ -304,9 +314,9 @@ impl JmapObject for EmailSubmission {
|
||||
|
||||
type Id = Id;
|
||||
|
||||
type Filter = ();
|
||||
type Filter = EmailSubmissionFilter;
|
||||
|
||||
type Comparator = ();
|
||||
type Comparator = EmailSubmissionComparator;
|
||||
|
||||
type GetArguments = ();
|
||||
|
||||
@@ -315,4 +325,107 @@ impl JmapObject for EmailSubmission {
|
||||
type QueryArguments = ();
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = EmailSubmissionProperty::Id;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum EmailSubmissionFilter {
|
||||
IdentityIds(Vec<Id>),
|
||||
EmailIds(Vec<Id>),
|
||||
ThreadIds(Vec<Id>),
|
||||
Before(UTCDate),
|
||||
After(UTCDate),
|
||||
UndoStatus(UndoStatus),
|
||||
_T(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum EmailSubmissionComparator {
|
||||
EmailId,
|
||||
ThreadId,
|
||||
SentAt,
|
||||
_T(String),
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for EmailSubmissionFilter {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
hashify::fnc_map!(key.as_bytes(),
|
||||
b"identityIds" => {
|
||||
*self = EmailSubmissionFilter::IdentityIds(map.next_value()?);
|
||||
},
|
||||
b"emailIds" => {
|
||||
*self = EmailSubmissionFilter::EmailIds(map.next_value()?);
|
||||
},
|
||||
b"threadIds" => {
|
||||
*self = EmailSubmissionFilter::ThreadIds(map.next_value()?);
|
||||
},
|
||||
b"before" => {
|
||||
*self = EmailSubmissionFilter::Before(map.next_value()?);
|
||||
},
|
||||
b"after" => {
|
||||
*self = EmailSubmissionFilter::After(map.next_value()?);
|
||||
},
|
||||
b"undoStatus" => {
|
||||
*self = EmailSubmissionFilter::UndoStatus(map.next_value()?);
|
||||
},
|
||||
_ => {
|
||||
*self = EmailSubmissionFilter::_T(key.to_string());
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for EmailSubmissionComparator {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
if key == "property" {
|
||||
let value = map.next_value::<Cow<str>>()?;
|
||||
hashify::fnc_map!(value.as_bytes(),
|
||||
|
||||
b"emailId" => {
|
||||
*self = EmailSubmissionComparator::EmailId;
|
||||
},
|
||||
b"threadId" => {
|
||||
*self = EmailSubmissionComparator::ThreadId;
|
||||
},
|
||||
b"sentAt" => {
|
||||
*self = EmailSubmissionComparator::SentAt;
|
||||
},
|
||||
_ => {
|
||||
*self = EmailSubmissionComparator::_T(key.to_string());
|
||||
}
|
||||
);
|
||||
} else {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EmailSubmissionFilter {
|
||||
fn default() -> Self {
|
||||
EmailSubmissionFilter::_T("".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EmailSubmissionComparator {
|
||||
fn default() -> Self {
|
||||
EmailSubmissionComparator::_T("".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Id> for EmailSubmissionValue {
|
||||
fn from(id: Id) -> Self {
|
||||
EmailSubmissionValue::Id(id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,4 +142,12 @@ impl JmapObject for Identity {
|
||||
type QueryArguments = ();
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = IdentityProperty::Id;
|
||||
}
|
||||
|
||||
impl From<Id> for IdentityValue {
|
||||
fn from(id: Id) -> Self {
|
||||
IdentityValue::Id(id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,9 +227,9 @@ impl JmapObject for Mailbox {
|
||||
|
||||
type Id = Id;
|
||||
|
||||
type Filter = ();
|
||||
type Filter = MailboxFilter;
|
||||
|
||||
type Comparator = ();
|
||||
type Comparator = MailboxComparator;
|
||||
|
||||
type GetArguments = ();
|
||||
|
||||
@@ -238,4 +238,115 @@ impl JmapObject for Mailbox {
|
||||
type QueryArguments = MailboxQueryArguments;
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = MailboxProperty::Id;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum MailboxFilter {
|
||||
Name(String),
|
||||
ParentId(Option<Id>),
|
||||
Role(Option<SpecialUse>),
|
||||
HasAnyRole(bool),
|
||||
IsSubscribed(bool),
|
||||
_T(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum MailboxComparator {
|
||||
SortOrder,
|
||||
Name,
|
||||
ParentId,
|
||||
_T(String),
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for MailboxFilter {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
hashify::fnc_map!(key.as_bytes(),
|
||||
b"name" => {
|
||||
*self = MailboxFilter::Name(map.next_value()?);
|
||||
},
|
||||
b"parentId" => {
|
||||
*self = MailboxFilter::ParentId(map.next_value()?);
|
||||
},
|
||||
b"role" => {
|
||||
*self = MailboxFilter::Role(map.next_value::<Option<RoleWrapper>>()?.map(|r| r.0));
|
||||
},
|
||||
b"hasAnyRole" => {
|
||||
*self = MailboxFilter::HasAnyRole(map.next_value()?);
|
||||
},
|
||||
b"isSubscribed" => {
|
||||
*self = MailboxFilter::IsSubscribed(map.next_value()?);
|
||||
},
|
||||
_ => {
|
||||
*self = MailboxFilter::_T(key.to_string());
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for MailboxComparator {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
if key == "property" {
|
||||
let value = map.next_value::<Cow<str>>()?;
|
||||
hashify::fnc_map!(value.as_bytes(),
|
||||
b"sortOrder" => {
|
||||
*self = MailboxComparator::SortOrder;
|
||||
},
|
||||
b"name" => {
|
||||
*self = MailboxComparator::Name;
|
||||
},
|
||||
b"parentId" => {
|
||||
*self = MailboxComparator::ParentId;
|
||||
},
|
||||
_ => {
|
||||
*self = MailboxComparator::_T(key.to_string());
|
||||
}
|
||||
);
|
||||
} else {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MailboxFilter {
|
||||
fn default() -> Self {
|
||||
MailboxFilter::_T("".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MailboxComparator {
|
||||
fn default() -> Self {
|
||||
MailboxComparator::_T("".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
struct RoleWrapper(SpecialUse);
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for RoleWrapper {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
SpecialUse::parse(<&str>::deserialize(deserializer)?)
|
||||
.map(RoleWrapper)
|
||||
.ok_or_else(|| serde::de::Error::custom("invalid JMAP role"))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Id> for MailboxValue {
|
||||
fn from(id: Id) -> Self {
|
||||
MailboxValue::Id(id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
use crate::request::deserialize::DeserializeArguments;
|
||||
use jmap_tools::{Element, Property};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::Serialize;
|
||||
use std::str::FromStr;
|
||||
|
||||
pub mod blob;
|
||||
@@ -24,16 +24,18 @@ pub mod vacation_response;
|
||||
|
||||
pub trait JmapObject {
|
||||
type Property: Property + FromStr + Serialize;
|
||||
type Element: Element<Property = Self::Property>;
|
||||
type Element: Element<Property = Self::Property> + From<Self::Id>;
|
||||
type Id: FromStr + Serialize;
|
||||
|
||||
type Filter: for<'de> Deserialize<'de>;
|
||||
type Comparator: for<'de> Deserialize<'de>;
|
||||
type Filter: Default + for<'de> DeserializeArguments<'de>;
|
||||
type Comparator: Default + for<'de> DeserializeArguments<'de>;
|
||||
|
||||
type GetArguments: Default + for<'de> DeserializeArguments<'de>;
|
||||
type SetArguments: Default + for<'de> DeserializeArguments<'de>;
|
||||
type QueryArguments: Default + for<'de> DeserializeArguments<'de>;
|
||||
type CopyArguments: Default + for<'de> DeserializeArguments<'de>;
|
||||
|
||||
const ID_PROPERTY: Self::Property;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
|
||||
@@ -8,7 +8,7 @@ use jmap_tools::{Element, Key, Property};
|
||||
use std::{borrow::Cow, str::FromStr};
|
||||
use types::id::Id;
|
||||
|
||||
use crate::object::JmapObject;
|
||||
use crate::{object::JmapObject, request::deserialize::DeserializeArguments};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Principal;
|
||||
@@ -141,9 +141,9 @@ impl JmapObject for Principal {
|
||||
|
||||
type Id = Id;
|
||||
|
||||
type Filter = ();
|
||||
type Filter = PrincipalFilter;
|
||||
|
||||
type Comparator = ();
|
||||
type Comparator = PrincipalComparator;
|
||||
|
||||
type GetArguments = ();
|
||||
|
||||
@@ -152,4 +152,116 @@ impl JmapObject for Principal {
|
||||
type QueryArguments = ();
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = PrincipalProperty::Id;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum PrincipalFilter {
|
||||
AccountIds(Vec<Id>),
|
||||
Email(String),
|
||||
Name(String),
|
||||
Text(String),
|
||||
Type(PrincipalType),
|
||||
Timezone(String),
|
||||
_T(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum PrincipalComparator {
|
||||
Name,
|
||||
Email,
|
||||
Type,
|
||||
_T(String),
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for PrincipalFilter {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
hashify::fnc_map!(key.as_bytes(),
|
||||
b"accountIds" => {
|
||||
*self = PrincipalFilter::AccountIds(map.next_value()?);
|
||||
},
|
||||
b"email" => {
|
||||
*self = PrincipalFilter::Email(map.next_value()?);
|
||||
},
|
||||
b"name" => {
|
||||
*self = PrincipalFilter::Name(map.next_value()?);
|
||||
},
|
||||
b"text" => {
|
||||
*self = PrincipalFilter::Text(map.next_value()?);
|
||||
},
|
||||
b"type" => {
|
||||
*self = PrincipalFilter::Type(map.next_value()?);
|
||||
},
|
||||
b"timezone" => {
|
||||
*self = PrincipalFilter::Timezone(map.next_value()?);
|
||||
},
|
||||
_ => {
|
||||
*self = PrincipalFilter::_T(key.to_string());
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for PrincipalComparator {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
if key == "property" {
|
||||
let value = map.next_value::<Cow<str>>()?;
|
||||
hashify::fnc_map!(value.as_bytes(),
|
||||
b"name" => {
|
||||
*self = PrincipalComparator::Name;
|
||||
},
|
||||
b"email" => {
|
||||
*self = PrincipalComparator::Email;
|
||||
},
|
||||
b"type" => {
|
||||
*self = PrincipalComparator::Type;
|
||||
},
|
||||
_ => {
|
||||
*self = PrincipalComparator::_T(key.to_string());
|
||||
}
|
||||
);
|
||||
} else {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PrincipalFilter {
|
||||
fn default() -> Self {
|
||||
PrincipalFilter::_T("".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PrincipalComparator {
|
||||
fn default() -> Self {
|
||||
PrincipalComparator::_T("".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for PrincipalType {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
PrincipalType::parse(<&str>::deserialize(deserializer)?)
|
||||
.ok_or_else(|| serde::de::Error::custom("invalid JMAP PrincipalType"))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Id> for PrincipalValue {
|
||||
fn from(id: Id) -> Self {
|
||||
PrincipalValue::Id(id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,4 +161,12 @@ impl JmapObject for PushSubscription {
|
||||
type QueryArguments = ();
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = PushSubscriptionProperty::Id;
|
||||
}
|
||||
|
||||
impl From<Id> for PushSubscriptionValue {
|
||||
fn from(id: Id) -> Self {
|
||||
PushSubscriptionValue::Id(id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,11 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{object::JmapObject, request::deserialize::DeserializeArguments};
|
||||
use jmap_tools::{Element, Key, Property};
|
||||
use std::{borrow::Cow, str::FromStr};
|
||||
use types::{id::Id, type_state::DataType};
|
||||
|
||||
use crate::object::JmapObject;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Quota;
|
||||
|
||||
@@ -119,9 +118,9 @@ impl JmapObject for Quota {
|
||||
|
||||
type Id = Id;
|
||||
|
||||
type Filter = ();
|
||||
type Filter = QuotaFilter;
|
||||
|
||||
type Comparator = ();
|
||||
type Comparator = QuotaComparator;
|
||||
|
||||
type GetArguments = ();
|
||||
|
||||
@@ -130,4 +129,98 @@ impl JmapObject for Quota {
|
||||
type QueryArguments = ();
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = QuotaProperty::Id;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum QuotaFilter {
|
||||
Name(String),
|
||||
Type(String),
|
||||
Scope(String),
|
||||
ResourceType(String),
|
||||
_T(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum QuotaComparator {
|
||||
Name,
|
||||
Type,
|
||||
Used,
|
||||
_T(String),
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for QuotaFilter {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
hashify::fnc_map!(key.as_bytes(),
|
||||
b"name" => {
|
||||
*self = QuotaFilter::Name(map.next_value()?);
|
||||
},
|
||||
b"type" => {
|
||||
*self = QuotaFilter::Type(map.next_value()?);
|
||||
},
|
||||
b"scope" => {
|
||||
*self = QuotaFilter::Scope(map.next_value()?);
|
||||
},
|
||||
b"resourceType" => {
|
||||
*self = QuotaFilter::ResourceType(map.next_value()?);
|
||||
},
|
||||
_ => {
|
||||
*self = QuotaFilter::_T(key.to_string());
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for QuotaComparator {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
if key == "property" {
|
||||
let value = map.next_value::<Cow<str>>()?;
|
||||
hashify::fnc_map!(value.as_bytes(),
|
||||
b"name" => {
|
||||
*self = QuotaComparator::Name;
|
||||
},
|
||||
b"type" => {
|
||||
*self = QuotaComparator::Type;
|
||||
},
|
||||
b"used" => {
|
||||
*self = QuotaComparator::Used;
|
||||
},
|
||||
_ => {
|
||||
*self = QuotaComparator::_T(key.to_string());
|
||||
}
|
||||
);
|
||||
} else {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for QuotaFilter {
|
||||
fn default() -> Self {
|
||||
QuotaFilter::_T("".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for QuotaComparator {
|
||||
fn default() -> Self {
|
||||
QuotaComparator::_T("".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Id> for QuotaValue {
|
||||
fn from(id: Id) -> Self {
|
||||
QuotaValue::Id(id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,9 +140,9 @@ impl JmapObject for Sieve {
|
||||
|
||||
type Id = Id;
|
||||
|
||||
type Filter = ();
|
||||
type Filter = SieveFilter;
|
||||
|
||||
type Comparator = ();
|
||||
type Comparator = SieveComparator;
|
||||
|
||||
type GetArguments = ();
|
||||
|
||||
@@ -151,4 +151,86 @@ impl JmapObject for Sieve {
|
||||
type QueryArguments = ();
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = SieveProperty::Id;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum SieveFilter {
|
||||
Name(String),
|
||||
IsActive(bool),
|
||||
_T(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum SieveComparator {
|
||||
Name,
|
||||
IsActive,
|
||||
_T(String),
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for SieveFilter {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
hashify::fnc_map!(key.as_bytes(),
|
||||
b"name" => {
|
||||
*self = SieveFilter::Name(map.next_value()?);
|
||||
},
|
||||
b"isActive" => {
|
||||
*self = SieveFilter::IsActive(map.next_value()?);
|
||||
},
|
||||
_ => {
|
||||
*self = SieveFilter::_T(key.to_string());
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for SieveComparator {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
if key == "property" {
|
||||
let value = map.next_value::<Cow<str>>()?;
|
||||
hashify::fnc_map!(value.as_bytes(),
|
||||
b"name" => {
|
||||
*self = SieveComparator::Name;
|
||||
},
|
||||
b"isActive" => {
|
||||
*self = SieveComparator::IsActive;
|
||||
},
|
||||
_ => {
|
||||
*self = SieveComparator::_T(key.to_string());
|
||||
}
|
||||
);
|
||||
} else {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SieveFilter {
|
||||
fn default() -> Self {
|
||||
SieveFilter::_T("".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SieveComparator {
|
||||
fn default() -> Self {
|
||||
SieveComparator::_T("".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Id> for SieveValue {
|
||||
fn from(id: Id) -> Self {
|
||||
SieveValue::Id(id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,4 +100,12 @@ impl JmapObject for Thread {
|
||||
type QueryArguments = ();
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = ThreadProperty::Id;
|
||||
}
|
||||
|
||||
impl From<Id> for ThreadValue {
|
||||
fn from(id: Id) -> Self {
|
||||
ThreadValue::Id(id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,4 +123,12 @@ impl JmapObject for VacationResponse {
|
||||
type QueryArguments = ();
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = VacationResponseProperty::Id;
|
||||
}
|
||||
|
||||
impl From<Id> for VacationResponseValue {
|
||||
fn from(id: Id) -> Self {
|
||||
VacationResponseValue::Id(id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ use serde::{
|
||||
de::{self, MapAccess, Visitor},
|
||||
};
|
||||
|
||||
pub(crate) trait DeserializeArguments<'de> {
|
||||
pub trait DeserializeArguments<'de> {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: MapAccess<'de>;
|
||||
|
||||
@@ -61,7 +61,7 @@ pub enum RequestMethod<'x> {
|
||||
ParseEmail(ParseEmailRequest),
|
||||
Query(QueryRequestMethod),
|
||||
QueryChanges(QueryChangesRequestMethod),
|
||||
SearchSnippet(GetSearchSnippetRequest<()>),
|
||||
SearchSnippet(GetSearchSnippetRequest),
|
||||
ValidateScript(ValidateSieveScriptRequest),
|
||||
LookupBlob(BlobLookupRequest),
|
||||
UploadBlob(BlobUploadRequest),
|
||||
@@ -170,3 +170,15 @@ impl Default for Request<'_> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> MaybeInvalid<T>
|
||||
where
|
||||
T: FromStr,
|
||||
{
|
||||
pub fn try_unwrap(self) -> Option<T> {
|
||||
match self {
|
||||
MaybeInvalid::Value(id) => Some(id),
|
||||
MaybeInvalid::Invalid(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -462,6 +462,45 @@ mod tests {
|
||||
}
|
||||
"#;
|
||||
|
||||
const TEST1: &str = r#"
|
||||
{
|
||||
"using": [
|
||||
"urn:ietf:params:jmap:core",
|
||||
"urn:ietf:params:jmap:mail"
|
||||
],
|
||||
"methodCalls": [
|
||||
[
|
||||
"Email/query",
|
||||
{
|
||||
"accountId": "0",
|
||||
"filter": { "conditions": [ { "hasKeyword": "music" }, { "hasKeyword": "video" }, { "operator": "AND", "conditions": [ { "subject": "test" }, { "minSize": 100 } ] } ], "operator": "OR" },
|
||||
"sort": [
|
||||
{
|
||||
"property": "subject",
|
||||
"isAscending": true
|
||||
},
|
||||
{
|
||||
"property": "allInThreadHaveKeyword",
|
||||
"isAscending": false,
|
||||
"keyword": "$seen"
|
||||
},
|
||||
{
|
||||
"keyword": "$junk",
|
||||
"property": "someInThreadHaveKeyword",
|
||||
"collation": "i;octet",
|
||||
"isAscending": false
|
||||
}
|
||||
],
|
||||
"position": 0,
|
||||
"limit": 10
|
||||
},
|
||||
"c1"
|
||||
]
|
||||
],
|
||||
"createdIds": {}
|
||||
}
|
||||
"#;
|
||||
|
||||
const TEST2: &str = r##"
|
||||
{
|
||||
"using": [
|
||||
@@ -557,6 +596,7 @@ mod tests {
|
||||
#[test]
|
||||
fn parse_request() {
|
||||
println!("{:#?}", Request::parse(TEST.as_bytes(), 10, 10240));
|
||||
println!("{:#?}", Request::parse(TEST1.as_bytes(), 10, 10240));
|
||||
println!("{:#?}", Request::parse(TEST2.as_bytes(), 10, 10240));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,3 +87,21 @@ impl<V: Default> Default for MaybeResultReference<V> {
|
||||
MaybeResultReference::Value(V::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Default> MaybeResultReference<T> {
|
||||
pub fn unwrap(self) -> T {
|
||||
match self {
|
||||
MaybeResultReference::Value(v) => v,
|
||||
MaybeResultReference::Reference(_) => T::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FromStr> MaybeIdReference<T> {
|
||||
pub fn try_unwrap(self) -> Option<T> {
|
||||
match self {
|
||||
MaybeIdReference::Id(id) => Some(id),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,7 @@ pub mod references;
|
||||
pub mod serialize;
|
||||
pub mod status;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use jmap_tools::{Null, Value};
|
||||
|
||||
use self::serialize::serialize_hex;
|
||||
use crate::{
|
||||
error::method::MethodErrorWrapper,
|
||||
method::{
|
||||
@@ -28,19 +25,23 @@ use crate::{
|
||||
upload::BlobUploadResponse,
|
||||
validate::ValidateSieveScriptResponse,
|
||||
},
|
||||
object::{
|
||||
blob::Blob, email::Email, email_submission::EmailSubmission, identity::Identity,
|
||||
mailbox::Mailbox, principal::Principal, push_subscription::PushSubscription, quota::Quota,
|
||||
sieve::Sieve, thread::Thread, vacation_response::VacationResponse,
|
||||
},
|
||||
request::{Call, method::MethodName},
|
||||
};
|
||||
|
||||
use self::serialize::serialize_hex;
|
||||
use jmap_tools::{Null, Value};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum ResponseMethod<'x> {
|
||||
/*Get(GetResponse),
|
||||
Set(SetResponse),
|
||||
Changes(ChangesResponse),
|
||||
Copy(CopyResponse),*/
|
||||
CopyBlob(CopyBlobResponse),
|
||||
Get(GetResponseMethod),
|
||||
Set(SetResponseMethod),
|
||||
Changes(ChangesResponseMethod),
|
||||
Copy(CopyResponseMethod),
|
||||
ImportEmail(ImportEmailResponse),
|
||||
ParseEmail(ParseEmailResponse),
|
||||
QueryChanges(QueryChangesResponse),
|
||||
@@ -53,6 +54,52 @@ pub enum ResponseMethod<'x> {
|
||||
Error(MethodErrorWrapper),
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum GetResponseMethod {
|
||||
Email(GetResponse<Email>),
|
||||
Mailbox(GetResponse<Mailbox>),
|
||||
Thread(GetResponse<Thread>),
|
||||
Identity(GetResponse<Identity>),
|
||||
EmailSubmission(GetResponse<EmailSubmission>),
|
||||
PushSubscription(GetResponse<PushSubscription>),
|
||||
Sieve(GetResponse<Sieve>),
|
||||
VacationResponse(GetResponse<VacationResponse>),
|
||||
Principal(GetResponse<Principal>),
|
||||
Quota(GetResponse<Quota>),
|
||||
Blob(GetResponse<Blob>),
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum SetResponseMethod {
|
||||
Email(SetResponse<Email>),
|
||||
Mailbox(SetResponse<Mailbox>),
|
||||
Identity(SetResponse<Identity>),
|
||||
EmailSubmission(SetResponse<EmailSubmission>),
|
||||
PushSubscription(SetResponse<PushSubscription>),
|
||||
Sieve(SetResponse<Sieve>),
|
||||
VacationResponse(SetResponse<VacationResponse>),
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum ChangesResponseMethod {
|
||||
Email(ChangesResponse<Email>),
|
||||
Mailbox(ChangesResponse<Mailbox>),
|
||||
Thread(ChangesResponse<Thread>),
|
||||
Identity(ChangesResponse<Identity>),
|
||||
EmailSubmission(ChangesResponse<EmailSubmission>),
|
||||
Quota(ChangesResponse<Quota>),
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum CopyResponseMethod {
|
||||
Email(CopyResponse<Email>),
|
||||
Blob(CopyBlobResponse),
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
pub struct Response<'x> {
|
||||
#[serde(rename = "methodResponses")]
|
||||
@@ -66,8 +113,8 @@ pub struct Response<'x> {
|
||||
#[serde(skip_serializing_if = "HashMap::is_empty")]
|
||||
pub created_ids: HashMap<String, String>,
|
||||
}
|
||||
/*
|
||||
impl Response<'_> {
|
||||
|
||||
impl<'x> Response<'x> {
|
||||
pub fn new(session_state: u32, created_ids: HashMap<String, String>, capacity: usize) -> Self {
|
||||
Response {
|
||||
session_state,
|
||||
@@ -80,7 +127,7 @@ impl Response<'_> {
|
||||
&mut self,
|
||||
id: String,
|
||||
name: MethodName,
|
||||
method: impl Into<ResponseMethod>,
|
||||
method: impl Into<ResponseMethod<'x>>,
|
||||
) {
|
||||
self.method_responses.push(Call {
|
||||
id,
|
||||
@@ -102,97 +149,13 @@ impl Response<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<trc::Error> for ResponseMethod {
|
||||
impl From<trc::Error> for ResponseMethod<'_> {
|
||||
fn from(error: trc::Error) -> Self {
|
||||
ResponseMethod::Error(error.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Echo> for ResponseMethod {
|
||||
fn from(echo: Echo) -> Self {
|
||||
ResponseMethod::Echo(echo)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GetResponse> for ResponseMethod {
|
||||
fn from(get: GetResponse) -> Self {
|
||||
ResponseMethod::Get(get)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SetResponse> for ResponseMethod {
|
||||
fn from(set: SetResponse) -> Self {
|
||||
ResponseMethod::Set(set)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ChangesResponse> for ResponseMethod {
|
||||
fn from(changes: ChangesResponse) -> Self {
|
||||
ResponseMethod::Changes(changes)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CopyResponse> for ResponseMethod {
|
||||
fn from(copy: CopyResponse) -> Self {
|
||||
ResponseMethod::Copy(copy)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CopyBlobResponse> for ResponseMethod {
|
||||
fn from(copy_blob: CopyBlobResponse) -> Self {
|
||||
ResponseMethod::CopyBlob(copy_blob)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ImportEmailResponse> for ResponseMethod {
|
||||
fn from(import_email: ImportEmailResponse) -> Self {
|
||||
ResponseMethod::ImportEmail(import_email)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseEmailResponse> for ResponseMethod {
|
||||
fn from(parse_email: ParseEmailResponse) -> Self {
|
||||
ResponseMethod::ParseEmail(parse_email)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<QueryChangesResponse> for ResponseMethod {
|
||||
fn from(query_changes: QueryChangesResponse) -> Self {
|
||||
ResponseMethod::QueryChanges(query_changes)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<QueryResponse> for ResponseMethod {
|
||||
fn from(query: QueryResponse) -> Self {
|
||||
ResponseMethod::Query(query)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GetSearchSnippetResponse> for ResponseMethod {
|
||||
fn from(search_snippet: GetSearchSnippetResponse) -> Self {
|
||||
ResponseMethod::SearchSnippet(search_snippet)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ValidateSieveScriptResponse> for ResponseMethod {
|
||||
fn from(validate_script: ValidateSieveScriptResponse) -> Self {
|
||||
ResponseMethod::ValidateScript(validate_script)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BlobUploadResponse> for ResponseMethod {
|
||||
fn from(upload_blob: BlobUploadResponse) -> Self {
|
||||
ResponseMethod::UploadBlob(upload_blob)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BlobLookupResponse> for ResponseMethod {
|
||||
fn from(lookup_blob: BlobLookupResponse) -> Self {
|
||||
ResponseMethod::LookupBlob(lookup_blob)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Into<ResponseMethod>> From<trc::Result<T>> for ResponseMethod {
|
||||
impl<'x, T: Into<ResponseMethod<'x>>> From<trc::Result<T>> for ResponseMethod<'x> {
|
||||
fn from(result: trc::Result<T>) -> Self {
|
||||
match result {
|
||||
Ok(value) => value.into(),
|
||||
@@ -200,4 +163,223 @@ impl<T: Into<ResponseMethod>> From<trc::Result<T>> for ResponseMethod {
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
impl<'x> From<GetResponse<Email>> for ResponseMethod<'x> {
|
||||
fn from(value: GetResponse<Email>) -> Self {
|
||||
ResponseMethod::Get(GetResponseMethod::Email(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<GetResponse<Mailbox>> for ResponseMethod<'x> {
|
||||
fn from(value: GetResponse<Mailbox>) -> Self {
|
||||
ResponseMethod::Get(GetResponseMethod::Mailbox(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<GetResponse<Thread>> for ResponseMethod<'x> {
|
||||
fn from(value: GetResponse<Thread>) -> Self {
|
||||
ResponseMethod::Get(GetResponseMethod::Thread(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<GetResponse<Identity>> for ResponseMethod<'x> {
|
||||
fn from(value: GetResponse<Identity>) -> Self {
|
||||
ResponseMethod::Get(GetResponseMethod::Identity(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<GetResponse<EmailSubmission>> for ResponseMethod<'x> {
|
||||
fn from(value: GetResponse<EmailSubmission>) -> Self {
|
||||
ResponseMethod::Get(GetResponseMethod::EmailSubmission(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<GetResponse<PushSubscription>> for ResponseMethod<'x> {
|
||||
fn from(value: GetResponse<PushSubscription>) -> Self {
|
||||
ResponseMethod::Get(GetResponseMethod::PushSubscription(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<GetResponse<Sieve>> for ResponseMethod<'x> {
|
||||
fn from(value: GetResponse<Sieve>) -> Self {
|
||||
ResponseMethod::Get(GetResponseMethod::Sieve(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<GetResponse<VacationResponse>> for ResponseMethod<'x> {
|
||||
fn from(value: GetResponse<VacationResponse>) -> Self {
|
||||
ResponseMethod::Get(GetResponseMethod::VacationResponse(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<GetResponse<Principal>> for ResponseMethod<'x> {
|
||||
fn from(value: GetResponse<Principal>) -> Self {
|
||||
ResponseMethod::Get(GetResponseMethod::Principal(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<GetResponse<Quota>> for ResponseMethod<'x> {
|
||||
fn from(value: GetResponse<Quota>) -> Self {
|
||||
ResponseMethod::Get(GetResponseMethod::Quota(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<GetResponse<Blob>> for ResponseMethod<'x> {
|
||||
fn from(value: GetResponse<Blob>) -> Self {
|
||||
ResponseMethod::Get(GetResponseMethod::Blob(value))
|
||||
}
|
||||
}
|
||||
|
||||
// Direct SetResponse conversions to ResponseMethod
|
||||
impl<'x> From<SetResponse<Email>> for ResponseMethod<'x> {
|
||||
fn from(value: SetResponse<Email>) -> Self {
|
||||
ResponseMethod::Set(SetResponseMethod::Email(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<SetResponse<Mailbox>> for ResponseMethod<'x> {
|
||||
fn from(value: SetResponse<Mailbox>) -> Self {
|
||||
ResponseMethod::Set(SetResponseMethod::Mailbox(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<SetResponse<Identity>> for ResponseMethod<'x> {
|
||||
fn from(value: SetResponse<Identity>) -> Self {
|
||||
ResponseMethod::Set(SetResponseMethod::Identity(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<SetResponse<EmailSubmission>> for ResponseMethod<'x> {
|
||||
fn from(value: SetResponse<EmailSubmission>) -> Self {
|
||||
ResponseMethod::Set(SetResponseMethod::EmailSubmission(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<SetResponse<PushSubscription>> for ResponseMethod<'x> {
|
||||
fn from(value: SetResponse<PushSubscription>) -> Self {
|
||||
ResponseMethod::Set(SetResponseMethod::PushSubscription(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<SetResponse<Sieve>> for ResponseMethod<'x> {
|
||||
fn from(value: SetResponse<Sieve>) -> Self {
|
||||
ResponseMethod::Set(SetResponseMethod::Sieve(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<SetResponse<VacationResponse>> for ResponseMethod<'x> {
|
||||
fn from(value: SetResponse<VacationResponse>) -> Self {
|
||||
ResponseMethod::Set(SetResponseMethod::VacationResponse(value))
|
||||
}
|
||||
}
|
||||
|
||||
// Direct ChangesResponse conversions to ResponseMethod
|
||||
impl<'x> From<ChangesResponse<Email>> for ResponseMethod<'x> {
|
||||
fn from(value: ChangesResponse<Email>) -> Self {
|
||||
ResponseMethod::Changes(ChangesResponseMethod::Email(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<ChangesResponse<Mailbox>> for ResponseMethod<'x> {
|
||||
fn from(value: ChangesResponse<Mailbox>) -> Self {
|
||||
ResponseMethod::Changes(ChangesResponseMethod::Mailbox(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<ChangesResponse<Thread>> for ResponseMethod<'x> {
|
||||
fn from(value: ChangesResponse<Thread>) -> Self {
|
||||
ResponseMethod::Changes(ChangesResponseMethod::Thread(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<ChangesResponse<Identity>> for ResponseMethod<'x> {
|
||||
fn from(value: ChangesResponse<Identity>) -> Self {
|
||||
ResponseMethod::Changes(ChangesResponseMethod::Identity(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<ChangesResponse<EmailSubmission>> for ResponseMethod<'x> {
|
||||
fn from(value: ChangesResponse<EmailSubmission>) -> Self {
|
||||
ResponseMethod::Changes(ChangesResponseMethod::EmailSubmission(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<ChangesResponse<Quota>> for ResponseMethod<'x> {
|
||||
fn from(value: ChangesResponse<Quota>) -> Self {
|
||||
ResponseMethod::Changes(ChangesResponseMethod::Quota(value))
|
||||
}
|
||||
}
|
||||
|
||||
// Direct CopyResponse conversions to ResponseMethod
|
||||
impl<'x> From<CopyResponse<Email>> for ResponseMethod<'x> {
|
||||
fn from(value: CopyResponse<Email>) -> Self {
|
||||
ResponseMethod::Copy(CopyResponseMethod::Email(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<CopyBlobResponse> for ResponseMethod<'x> {
|
||||
fn from(value: CopyBlobResponse) -> Self {
|
||||
ResponseMethod::Copy(CopyResponseMethod::Blob(value))
|
||||
}
|
||||
}
|
||||
|
||||
// Other direct conversions
|
||||
impl<'x> From<ImportEmailResponse> for ResponseMethod<'x> {
|
||||
fn from(value: ImportEmailResponse) -> Self {
|
||||
ResponseMethod::ImportEmail(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<ParseEmailResponse> for ResponseMethod<'x> {
|
||||
fn from(value: ParseEmailResponse) -> Self {
|
||||
ResponseMethod::ParseEmail(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<QueryChangesResponse> for ResponseMethod<'x> {
|
||||
fn from(value: QueryChangesResponse) -> Self {
|
||||
ResponseMethod::QueryChanges(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<QueryResponse> for ResponseMethod<'x> {
|
||||
fn from(value: QueryResponse) -> Self {
|
||||
ResponseMethod::Query(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<GetSearchSnippetResponse> for ResponseMethod<'x> {
|
||||
fn from(value: GetSearchSnippetResponse) -> Self {
|
||||
ResponseMethod::SearchSnippet(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<ValidateSieveScriptResponse> for ResponseMethod<'x> {
|
||||
fn from(value: ValidateSieveScriptResponse) -> Self {
|
||||
ResponseMethod::ValidateScript(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<BlobLookupResponse> for ResponseMethod<'x> {
|
||||
fn from(value: BlobLookupResponse) -> Self {
|
||||
ResponseMethod::LookupBlob(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<BlobUploadResponse> for ResponseMethod<'x> {
|
||||
fn from(value: BlobUploadResponse) -> Self {
|
||||
ResponseMethod::UploadBlob(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<Value<'x, Null, Null>> for ResponseMethod<'x> {
|
||||
fn from(value: Value<'x, Null, Null>) -> Self {
|
||||
ResponseMethod::Echo(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<MethodErrorWrapper> for ResponseMethod<'x> {
|
||||
fn from(value: MethodErrorWrapper) -> Self {
|
||||
ResponseMethod::Error(value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,6 +323,26 @@ impl SieveScriptSet for Server {
|
||||
self.sieve_activate_script(account_id, None).await?
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
pub fn get_object_by_id(&mut self, id: Id) -> Option<&mut Value<'x, P, E>> {
|
||||
if let Some(obj) = self.updated.get_mut(&id) {
|
||||
if let Some(obj) = obj {
|
||||
return Some(obj);
|
||||
} else {
|
||||
*obj = Some(Object::with_capacity(1));
|
||||
return obj.as_mut().unwrap().into();
|
||||
}
|
||||
}
|
||||
|
||||
(&mut self.created)
|
||||
.into_iter()
|
||||
.map(|(_, obj)| obj)
|
||||
.find(|obj| obj.0.get(&Property::Id) == Some(&Value::Id(id)))
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
if !changed_ids.is_empty() {
|
||||
for (document_id, is_active) in changed_ids {
|
||||
if let Some(obj) = ctx.response.get_object_by_id(Id::from(document_id)) {
|
||||
|
||||
Reference in New Issue
Block a user