summaryrefslogtreecommitdiffstats
path: root/melib
diff options
context:
space:
mode:
authorManos Pitsidianakis <el13635@mail.ntua.gr>2019-12-04 01:04:38 +0200
committerManos Pitsidianakis <el13635@mail.ntua.gr>2019-12-13 00:04:58 +0200
commita1efeed34352e60434e511edc5d35ffb4c6b4276 (patch)
tree2471d72c72994d548ab74c940cd5eb3765b12b86 /melib
parente8611cca2f43c6027a934d6a995324d6b0012d0e (diff)
JMAP WIP #3
Diffstat (limited to 'melib')
-rw-r--r--melib/src/backends/jmap.rs72
-rw-r--r--melib/src/backends/jmap/connection.rs64
-rw-r--r--melib/src/backends/jmap/objects/email.rs64
-rw-r--r--melib/src/backends/jmap/protocol.rs134
-rw-r--r--melib/src/backends/jmap/rfc8620.rs105
-rw-r--r--melib/src/backends/jmap/rfc8620/comparator.rs17
6 files changed, 344 insertions, 112 deletions
diff --git a/melib/src/backends/jmap.rs b/melib/src/backends/jmap.rs
index 26d850d0..8d25e982 100644
--- a/melib/src/backends/jmap.rs
+++ b/melib/src/backends/jmap.rs
@@ -35,6 +35,19 @@ use std::hash::Hasher;
use std::str::FromStr;
use std::sync::{Arc, Mutex, RwLock};
+#[macro_export]
+macro_rules! _impl {
+ ($field:ident : $t:ty) => {
+ pub fn $field(mut self, new_val: $t) -> Self {
+ self.$field = new_val;
+ self
+ }
+ }
+ }
+
+pub mod connection;
+use connection::*;
+
pub mod protocol;
use protocol::*;
@@ -122,7 +135,7 @@ macro_rules! get_conf_val {
($s:ident[$var:literal]) => {
$s.extra.get($var).ok_or_else(|| {
MeliError::new(format!(
- "Configuration error ({}): IMAP connection requires the field `{}` set",
+ "Configuration error ({}): JMAP connection requires the field `{}` set",
$s.name.as_str(),
$var
))
@@ -152,7 +165,7 @@ pub struct JmapType {
online: Arc<Mutex<bool>>,
is_subscribed: Arc<IsSubscribedFn>,
server_conf: JmapServerConf,
- connection: Arc<Mutex<JmapConnection>>,
+ connection: Arc<JmapConnection>,
folders: Arc<RwLock<FnvHashMap<FolderHash, JmapFolder>>>,
}
@@ -168,15 +181,11 @@ impl MailBackend for JmapType {
let handle = {
let tx = w.tx();
let closure = move |_work_context| {
- let mut conn_lck = connection.lock().unwrap();
tx.send(AsyncStatus::Payload(
- protocol::get_message_list(
- &mut conn_lck,
- &folders.read().unwrap()[&folder_hash],
- )
- .and_then(|ids| {
- protocol::get_message(&mut conn_lck, std::dbg!(&ids).as_slice())
- }),
+ protocol::get_message_list(&connection, &folders.read().unwrap()[&folder_hash])
+ .and_then(|ids| {
+ protocol::get_message(&connection, std::dbg!(&ids).as_slice())
+ }),
))
.unwrap();
tx.send(AsyncStatus::Finished).unwrap();
@@ -196,9 +205,7 @@ impl MailBackend for JmapType {
fn folders(&self) -> Result<FnvHashMap<FolderHash, Folder>> {
if self.folders.read().unwrap().is_empty() {
- let folders = std::dbg!(protocol::get_mailboxes(
- &mut self.connection.lock().unwrap()
- ))?;
+ let folders = std::dbg!(protocol::get_mailboxes(&self.connection))?;
let ret = Ok(folders
.iter()
.map(|(&h, f)| (h, BackendFolder::clone(f) as Folder))
@@ -242,10 +249,7 @@ impl JmapType {
let server_conf = JmapServerConf::new(s)?;
Ok(Box::new(JmapType {
- connection: Arc::new(Mutex::new(JmapConnection::new(
- &server_conf,
- online.clone(),
- )?)),
+ connection: Arc::new(JmapConnection::new(&server_conf, online.clone())?),
folders: Arc::new(RwLock::new(FnvHashMap::default())),
account_name: s.name.clone(),
online,
@@ -263,37 +267,3 @@ impl JmapType {
Ok(())
}
}
-
-#[derive(Debug)]
-pub struct JmapConnection {
- request_no: usize,
- client: Client,
- online_status: Arc<Mutex<bool>>,
-}
-
-impl JmapConnection {
- pub fn new(server_conf: &JmapServerConf, online_status: Arc<Mutex<bool>>) -> Result<Self> {
- use reqwest::header;
- let mut headers = header::HeaderMap::new();
- headers.insert(
- header::AUTHORIZATION,
- header::HeaderValue::from_static("fc32dffe-14e7-11ea-a277-2477037a1804"),
- );
- headers.insert(
- header::ACCEPT,
- header::HeaderValue::from_static("application/json"),
- );
- headers.insert(
- header::CONTENT_TYPE,
- header::HeaderValue::from_static("application/json"),
- );
- Ok(JmapConnection {
- request_no: 0,
- client: reqwest::blocking::ClientBuilder::new()
- .danger_accept_invalid_certs(server_conf.danger_accept_invalid_certs)
- .default_headers(headers)
- .build()?,
- online_status,
- })
- }
-}
diff --git a/melib/src/backends/jmap/connection.rs b/melib/src/backends/jmap/connection.rs
new file mode 100644
index 00000000..036abd82
--- /dev/null
+++ b/melib/src/backends/jmap/connection.rs
@@ -0,0 +1,64 @@
+/*
+ * meli - jmap module.
+ *
+ * Copyright 2019 Manos Pitsidianakis
+ *
+ * This file is part of meli.
+ *
+ * meli is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * meli is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with meli. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+use super::*;
+
+#[derive(Debug)]
+pub struct JmapConnection {
+ pub request_no: Arc<Mutex<usize>>,
+ pub client: Arc<Mutex<Client>>,
+ pub online_status: Arc<Mutex<bool>>,
+ pub server_conf: JmapServerConf,
+}
+
+impl JmapConnection {
+ pub fn new(server_conf: &JmapServerConf, online_status: Arc<Mutex<bool>>) -> Result<Self> {
+ use reqwest::header;
+ let mut headers = header::HeaderMap::new();
+ headers.insert(
+ header::AUTHORIZATION,
+ header::HeaderValue::from_static("fc32dffe-14e7-11ea-a277-2477037a1804"),
+ );
+ headers.insert(
+ header::ACCEPT,
+ header::HeaderValue::from_static("application/json"),
+ );
+ headers.insert(
+ header::CONTENT_TYPE,
+ header::HeaderValue::from_static("application/json"),
+ );
+ let client = reqwest::blocking::ClientBuilder::new()
+ .danger_accept_invalid_certs(server_conf.danger_accept_invalid_certs)
+ .default_headers(headers)
+ .build()?;
+
+ let res_text = client.get(&server_conf.server_hostname).send()?.text()?;
+ debug!(&res_text);
+
+ let server_conf = server_conf.clone();
+ Ok(JmapConnection {
+ request_no: Arc::new(Mutex::new(0)),
+ client: Arc::new(Mutex::new(client)),
+ online_status,
+ server_conf,
+ })
+ }
+}
diff --git a/melib/src/backends/jmap/objects/email.rs b/melib/src/backends/jmap/objects/email.rs
index 851e41c0..d10d40e4 100644
--- a/melib/src/backends/jmap/objects/email.rs
+++ b/melib/src/backends/jmap/objects/email.rs
@@ -21,6 +21,7 @@
use super::*;
use crate::backends::jmap::protocol::*;
+use crate::backends::jmap::rfc8620::bool_false;
use std::collections::HashMap;
// 4.1.1.
@@ -151,7 +152,7 @@ pub struct EmailQueryResponse {
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct EmailQueryCall {
- pub filter: Vec<EmailFilterCondition>, /* "inMailboxes": [ folder.id ] },*/
+ pub filter: EmailFilterCondition, /* "inMailboxes": [ folder.id ] },*/
pub collapse_threads: bool,
pub position: u64,
pub fetch_threads: bool,
@@ -166,18 +167,44 @@ impl Method<EmailObject> for EmailQueryCall {
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct EmailGetCall {
- pub filter: Vec<EmailFilterCondition>, /* "inMailboxes": [ folder.id ] },*/
- pub collapse_threads: bool,
- pub position: u64,
- pub fetch_threads: bool,
- pub fetch_messages: bool,
- pub fetch_message_properties: Vec<MessageProperty>,
+ #[serde(flatten)]
+ pub get_call: GetCall<EmailObject>,
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ pub body_properties: Vec<String>,
+ #[serde(default = "bool_false")]
+ pub fetch_text_body_values: bool,
+ #[serde(default = "bool_false")]
+ pub fetch_html_body_values: bool,
+ #[serde(default = "bool_false")]
+ pub fetch_all_body_values: bool,
+ #[serde(default)]
+ pub max_body_value_bytes: u64,
}
impl Method<EmailObject> for EmailGetCall {
const NAME: &'static str = "Email/get";
}
+impl EmailGetCall {
+ pub fn new(get_call: GetCall<EmailObject>) -> Self {
+ EmailGetCall {
+ get_call,
+ body_properties: Vec::new(),
+ fetch_text_body_values: false,
+ fetch_html_body_values: false,
+ fetch_all_body_values: false,
+ max_body_value_bytes: 0,
+ }
+ }
+
+ _impl!(get_call: GetCall<EmailObject>);
+ _impl!(body_properties: Vec<String>);
+ _impl!(fetch_text_body_values: bool);
+ _impl!(fetch_html_body_values: bool);
+ _impl!(fetch_all_body_values: bool);
+ _impl!(max_body_value_bytes: u64);
+}
+
#[derive(Serialize, Deserialize, Default, Debug)]
#[serde(rename_all = "camelCase")]
pub struct EmailFilterCondition {
@@ -225,6 +252,29 @@ pub struct EmailFilterCondition {
pub header: Vec<String>,
}
+impl EmailFilterCondition {
+ _impl!(in_mailboxes: Vec<Id>);
+ _impl!(in_mailbox_other_than: Vec<Id>);
+ _impl!(before: UtcDate);
+ _impl!(after: UtcDate);
+ _impl!(min_size: Option<u64>);
+ _impl!(max_size: Option<u64>);
+ _impl!(all_in_thread_have_keyword: String);
+ _impl!(some_in_thread_have_keyword: String);
+ _impl!(none_in_thread_have_keyword: String);
+ _impl!(has_keyword: String);
+ _impl!(not_keyword: String);
+ _impl!(has_attachment: Option<bool>);
+ _impl!(text: String);
+ _impl!(from: String);
+ _impl!(to: String);
+ _impl!(cc: String);
+ _impl!(bcc: String);
+ _impl!(subject: String);
+ _impl!(body: String);
+ _impl!(header: Vec<String>);
+}
+
impl FilterTrait<EmailObject> for EmailFilterCondition {}
// The following convenience properties are also specified for the Email
diff --git a/melib/src/backends/jmap/protocol.rs b/melib/src/backends/jmap/protocol.rs
index 125e9366..8557f253 100644
--- a/melib/src/backends/jmap/protocol.rs
+++ b/melib/src/backends/jmap/protocol.rs
@@ -29,6 +29,15 @@ pub type UtcDate = String;
use super::rfc8620::Object;
+macro_rules! get_request_no {
+ ($lock:expr) => {{
+ let mut lck = $lock.lock().unwrap();
+ let ret = *lck;
+ *lck += 1;
+ ret
+ }};
+}
+
pub trait Method<OBJ: Object>: Serialize {
const NAME: &'static str;
}
@@ -68,19 +77,25 @@ pub struct Request {
/* Why is this Value instead of Box<dyn Method<_>>? The Method trait cannot be made into a
* Trait object because its serialize() will be generic. */
method_calls: Vec<Value>,
+
+ #[serde(skip_serializing)]
+ request_no: Arc<Mutex<usize>>,
}
impl Request {
- pub fn new() -> Self {
+ pub fn new(request_no: Arc<Mutex<usize>>) -> Self {
Request {
using: USING,
method_calls: Vec::new(),
+ request_no,
}
}
- pub fn add_call<M: Method<O>, O: Object>(&mut self, call: M) {
+ pub fn add_call<M: Method<O>, O: Object>(&mut self, call: M) -> usize {
+ let seq = get_request_no!(self.request_no);
self.method_calls
- .push(serde_json::to_value((M::NAME, call, "f")).unwrap());
+ .push(serde_json::to_value((M::NAME, call, &format!("m{}", seq))).unwrap());
+ seq
}
}
@@ -100,17 +115,19 @@ pub enum MethodCall {
Empty {},
}
-pub fn get_mailboxes(conn: &mut JmapConnection) -> Result<FnvHashMap<FolderHash, JmapFolder>> {
+pub fn get_mailboxes(conn: &JmapConnection) -> Result<FnvHashMap<FolderHash, JmapFolder>> {
+ let seq = get_request_no!(conn.request_no);
let res = conn
.client
+ .lock()
+ .unwrap()
.post("https://jmap-proxy.local/jmap/fc32dffe-14e7-11ea-a277-2477037a1804/")
.json(&json!({
"using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
"methodCalls": [["Mailbox/get", {},
- format!("#m{}", conn.request_no + 1).as_str()]],
+ format!("#m{}",seq).as_str()]],
}))
.send();
- conn.request_no += 1;
let mut v: JsonResponse =
serde_json::from_str(&std::dbg!(res.unwrap().text().unwrap())).unwrap();
@@ -259,12 +276,13 @@ pub struct JmapRights {
// fetchSearchSnippets: false
// }, "call1"]
// ]
-pub fn get_message_list(conn: &mut JmapConnection, folder: &JmapFolder) -> Result<Vec<String>> {
+pub fn get_message_list(conn: &JmapConnection, folder: &JmapFolder) -> Result<Vec<String>> {
+ let seq = get_request_no!(conn.request_no);
let email_call: EmailQueryCall = EmailQueryCall {
- filter: vec![EmailFilterCondition {
+ filter: EmailFilterCondition {
in_mailboxes: vec![folder.id.clone()],
..Default::default()
- }],
+ },
collapse_threads: false,
position: 0,
fetch_threads: true,
@@ -285,9 +303,8 @@ pub fn get_message_list(conn: &mut JmapConnection, folder: &JmapFolder) -> Resul
],
};
- let mut req = Request::new();
+ let mut req = Request::new(conn.request_no.clone());
req.add_call(email_call);
- std::dbg!(serde_json::to_string(&req));
/*
{
@@ -328,37 +345,43 @@ pub fn get_message_list(conn: &mut JmapConnection, folder: &JmapFolder) -> Resul
]]
}
*/
+ /*
+ r#"
+ "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+ "methodCalls": [["Email/query", { "filter": {
+ "inMailboxes": [ folder.id ]
+ },
+ "collapseThreads": false,
+ "position": 0,
+ "fetchThreads": true,
+ "fetchMessages": true,
+ "fetchMessageProperties": [
+ "threadId",
+ "mailboxId",
+ "isUnread",
+ "isFlagged",
+ "isAnswered",
+ "isDraft",
+ "hasAttachment",
+ "from",
+ "to",
+ "subject",
+ "date",
+ "preview"
+ ],
+ }, format!("m{}", seq).as_str()]],
+ });"
+ );*/
+
+ std::dbg!(serde_json::to_string(&req));
let res = conn
.client
+ .lock()
+ .unwrap()
.post("https://jmap-proxy.local/jmap/fc32dffe-14e7-11ea-a277-2477037a1804/")
- .json(&json!({
- "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
- "methodCalls": [["Email/query", { "filter": {
- "inMailboxes": [ folder.id ]
- },
- "collapseThreads": false,
- "position": 0,
- "fetchThreads": true,
- "fetchMessages": true,
- "fetchMessageProperties": [
- "threadId",
- "mailboxId",
- "isUnread",
- "isFlagged",
- "isAnswered",
- "isDraft",
- "hasAttachment",
- "from",
- "to",
- "subject",
- "date",
- "preview"
- ],
- }, format!("#m{}", conn.request_no + 1).as_str()]],
- }))
+ .json(&req)
.send();
- conn.request_no += 1;
let mut v: JsonResponse = serde_json::from_str(&std::dbg!(res.unwrap().text().unwrap()))?;
let result: Response = v.method_responses.remove(0).1;
@@ -369,11 +392,35 @@ pub fn get_message_list(conn: &mut JmapConnection, folder: &JmapFolder) -> Resul
}
}
-pub fn get_message(conn: &mut JmapConnection, ids: &[String]) -> Result<Vec<Envelope>> {
+pub fn get_message(conn: &JmapConnection, ids: &[String]) -> Result<Vec<Envelope>> {
+ let seq = get_request_no!(conn.request_no);
+ let email_call: EmailGetCall =
+ EmailGetCall::new(GetCall::new().ids(Some(ids.iter().cloned().collect::<Vec<String>>())));
+
+ let mut req = Request::new(conn.request_no.clone());
+ req.add_call(email_call);
let res = conn
.client
+ .lock()
+ .unwrap()
.post("https://jmap-proxy.local/jmap/fc32dffe-14e7-11ea-a277-2477037a1804/")
- .json(&json!({
+ .json(&req)
+ .send();
+
+ let res_text = res?.text()?;
+ let v: JsonResponse = serde_json::from_str(&res_text)?;
+ let mut f = std::fs::File::create(std::dbg!(format!("/tmp/asdfsa{}", seq))).unwrap();
+ use std::io::Write;
+ f.write_all(
+ serde_json::to_string_pretty(&serde_json::from_str::<Value>(&res_text)?)?.as_bytes(),
+ )
+ .unwrap();
+ Ok(vec![])
+}
+
+/*
+ *
+ *json!({
"using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
"methodCalls": [["Email/get", {
"ids": ids,
@@ -383,12 +430,7 @@ pub fn get_message(conn: &mut JmapConnection, ids: &[String]) -> Result<Vec<Enve
"bodyProperties": [ "partId", "blobId", "size", "type" ],
"fetchHTMLBodyValues": true,
"maxBodyValueBytes": 256
- }, format!("#m{}", conn.request_no + 1).as_str()]],
+ }, format!("m{}", seq).as_str()]],
}))
- .send();
- conn.request_no += 1;
- let v: JsonResponse = serde_json::from_str(&std::dbg!(res.unwrap().text().unwrap()))?;
- std::dbg!(&v);
- Ok(vec![])
-}
+*/
diff --git a/melib/src/backends/jmap/rfc8620.rs b/melib/src/backends/jmap/rfc8620.rs
index 55863b6c..f8cb0ce4 100644
--- a/melib/src/backends/jmap/rfc8620.rs
+++ b/melib/src/backends/jmap/rfc8620.rs
@@ -19,14 +19,18 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
+use super::Id;
use core::marker::PhantomData;
use serde::{de::DeserializeOwned, Serialize};
+use serde_json::Value;
+
mod filters;
pub use filters::*;
mod comparator;
pub use comparator::*;
use super::protocol::Method;
+use std::collections::HashMap;
pub trait Object {}
// 5.1. /get
@@ -57,20 +61,76 @@ pub trait Object {}
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
-pub struct GetCall<OBJ: Object, CALL: Method<OBJ>>
+pub struct JmapSession {
+ capabilities: HashMap<String, CapabilitiesObject>,
+ accounts: HashMap<Id, Account>,
+ primary_accounts: Vec<Id>,
+ username: String,
+ api_url: String,
+ download_url: String,
+
+ upload_url: String,
+ event_source_url: String,
+ state: String,
+ #[serde(flatten)]
+ extra_properties: HashMap<String, Value>,
+}
+
+#[derive(Deserialize, Serialize, Debug)]
+#[serde(rename_all = "camelCase")]
+pub struct CapabilitiesObject {
+ max_size_upload: u64,
+ max_concurrent_upload: u64,
+ max_size_request: u64,
+ max_concurrent_requests: u64,
+ max_calls_in_request: u64,
+ max_objects_in_get: u64,
+ max_objects_in_set: u64,
+ collation_algorithms: Vec<String>,
+}
+
+#[derive(Deserialize, Serialize, Debug)]
+#[serde(rename_all = "camelCase")]
+pub struct Account {
+ name: String,
+ is_personal: bool,
+ is_read_only: bool,
+ account_capabilities: HashMap<String, Value>,
+ #[serde(flatten)]
+ extra_properties: HashMap<String, Value>,
+}
+
+#[derive(Deserialize, Serialize, Debug)]
+#[serde(rename_all = "camelCase")]
+pub struct GetCall<OBJ: Object>
where
OBJ: std::fmt::Debug + Serialize,
{
#[serde(skip_serializing_if = "String::is_empty")]
- account_id: String,
+ pub account_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
- ids: Option<Vec<String>>,
+ pub ids: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
- properties: Option<Vec<String>>,
- _ph: PhantomData<*const CALL>,
- __ph: PhantomData<*const OBJ>,
+ pub properties: Option<Vec<String>>,
+ _ph: PhantomData<*const OBJ>,
}
+impl<OBJ: Object> GetCall<OBJ>
+where
+ OBJ: std::fmt::Debug + Serialize,
+{
+ pub fn new() -> Self {
+ Self {
+ account_id: String::new(),
+ ids: None,
+ properties: None,
+ _ph: PhantomData,
+ }
+ }
+ _impl!(account_id: String);
+ _impl!(ids: Option<Vec<String>>);
+ _impl!(properties: Option<Vec<String>>);
+}
// The response has the following arguments:
//
// o accountId: "Id"
@@ -128,6 +188,7 @@ pub struct GetResponse<T> {
enum JmapError {
RequestTooLarge,
InvalidArguments,
+ InvalidResultReference,
}
// 5.5. /query
@@ -321,11 +382,39 @@ where
_ph: PhantomData<*const OBJ>,
}
-fn bool_false() -> bool {
+impl<F: FilterTrait<OBJ>, OBJ: Object> QueryCall<F, OBJ>
+where
+ OBJ: std::fmt::Debug + Serialize,
+{
+ pub fn new() -> Self {
+ Self {
+ account_id: String::new(),
+ filter: None,
+ sort: None,
+ position: 0,
+ anchor: None,
+ anchor_offset: 0,
+ limit: None,
+ calculate_total: false,
+ _ph: PhantomData,
+ }
+ }
+
+ _impl!(account_id: String);
+ _impl!(filter: Option<Filter<F, OBJ>>);
+ _impl!(sort: Option<Comparator<OBJ>>);
+ _impl!(position: u64);
+ _impl!(anchor: Option<String>);
+ _impl!(anchor_offset: u64);
+ _impl!(limit: Option<u64>);
+ _impl!(calculate_total: bool);
+}
+
+pub fn bool_false() -> bool {
false
}
-fn bool_true() -> bool {
+pub fn bool_true() -> bool {
true
}
diff --git a/melib/src/backends/jmap/rfc8620/comparator.rs b/melib/src/backends/jmap/rfc8620/comparator.rs
index c11ad266..e1bad283 100644
--- a/melib/src/backends/jmap/rfc8620/comparator.rs
+++ b/melib/src/backends/jmap/rfc8620/comparator.rs
@@ -35,6 +35,23 @@ pub struct Comparator<OBJ: Object> {
_ph: PhantomData<*const OBJ>,
}
+impl<OBJ: Object> Comparator<OBJ> {
+ pub fn new() -> Self {
+ Self {
+ property: String::new(),
+ is_ascending: true,
+ collation: None,
+ additional_properties: Vec::new(),
+ _ph: PhantomData,
+ }
+ }
+
+ _impl!(property: String);
+ _impl!(is_ascending: bool);
+ _impl!(collation: Option<String>);
+ _impl!(additional_properties: Vec<String>);
+}
+
#[derive(Serialize, Debug)]
#[serde(rename_all = "UPPERCASE")]
pub enum FilterOperator {