JMAP protocol layer refactoring (part 3)

This commit is contained in:
mdecimus
2025-09-25 19:42:12 +02:00
parent a95010c42e
commit efa83c2255
24 changed files with 1599 additions and 771 deletions

View File

@@ -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> {
}
}
}
*/

View File

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

View File

@@ -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()?;

View File

@@ -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(),

View File

@@ -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()
}
}
*/

View File

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