diff --git a/melib/src/backends/jmap.rs b/melib/src/backends/jmap.rs
index ecb162d9..26d850d0 100644
--- a/melib/src/backends/jmap.rs
+++ b/melib/src/backends/jmap.rs
@@ -36,11 +36,15 @@ use std::str::FromStr;
use std::sync::{Arc, Mutex, RwLock};
pub mod protocol;
-
use protocol::*;
-pub mod folder;
+pub mod rfc8620;
+use rfc8620::*;
+pub mod objects;
+use objects::*;
+
+pub mod folder;
use folder::*;
#[derive(Debug, Default)]
diff --git a/melib/src/backends/jmap/objects.rs b/melib/src/backends/jmap/objects.rs
new file mode 100644
index 00000000..cc29d5b5
--- /dev/null
+++ b/melib/src/backends/jmap/objects.rs
@@ -0,0 +1,25 @@
+/*
+ * 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 .
+ */
+
+use super::*;
+
+mod email;
+pub use email::*;
diff --git a/melib/src/backends/jmap/objects/email.rs b/melib/src/backends/jmap/objects/email.rs
new file mode 100644
index 00000000..851e41c0
--- /dev/null
+++ b/melib/src/backends/jmap/objects/email.rs
@@ -0,0 +1,289 @@
+/*
+ * 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 .
+ */
+
+use super::*;
+use crate::backends::jmap::protocol::*;
+use std::collections::HashMap;
+
+// 4.1.1.
+// Metadata
+// These properties represent metadata about the message in the mail
+// store and are not derived from parsing the message itself.
+//
+// o id: "Id" (immutable; server-set)
+//
+// The id of the Email object. Note that this is the JMAP object id,
+// NOT the Message-ID header field value of the message [RFC5322].
+//
+// o blobId: "Id" (immutable; server-set)
+//
+// The id representing the raw octets of the message [RFC5322] for
+// this Email. This may be used to download the raw original message
+// or to attach it directly to another Email, etc.
+//
+// o threadId: "Id" (immutable; server-set)
+//
+// The id of the Thread to which this Email belongs.
+//
+// o mailboxIds: "Id[Boolean]"
+//
+// The set of Mailbox ids this Email belongs to. An Email in the
+// mail store MUST belong to one or more Mailboxes at all times
+// (until it is destroyed). The set is represented as an object,
+// with each key being a Mailbox id. The value for each key in the
+// object MUST be true.
+//
+// o keywords: "String[Boolean]" (default: {})
+//
+// A set of keywords that apply to the Email. The set is represented
+// as an object, with the keys being the keywords. The value for
+// each key in the object MUST be true.
+//
+// Keywords are shared with IMAP. The six system keywords from IMAP
+// get special treatment. The following four keywords have their
+// first character changed from "\" in IMAP to "$" in JMAP and have
+// particular semantic meaning:
+//
+// * "$draft": The Email is a draft the user is composing.
+//
+// * "$seen": The Email has been read.
+//
+// * "$flagged": The Email has been flagged for urgent/special
+// attention.
+//
+// * "$answered": The Email has been replied to.
+//
+// The IMAP "\Recent" keyword is not exposed via JMAP. The IMAP
+// "\Deleted" keyword is also not present: IMAP uses a delete+expunge
+// model, which JMAP does not. Any message with the "\Deleted"
+// keyword MUST NOT be visible via JMAP (and so are not counted in
+// the "totalEmails", "unreadEmails", "totalThreads", and
+// "unreadThreads" Mailbox properties).
+//
+// Users may add arbitrary keywords to an Email. For compatibility
+// with IMAP, a keyword is a case-insensitive string of 1-255
+// characters in the ASCII subset %x21-%x7e (excludes control chars
+// and space), and it MUST NOT include any of these characters:
+//
+// ( ) { ] % * " \
+//
+// Because JSON is case sensitive, servers MUST return keywords in
+// lowercase.
+//
+// The IANA "IMAP and JMAP Keywords" registry at
+// as
+// established in [RFC5788] assigns semantic meaning to some other
+// keywords in common use. New keywords may be established here in
+// the future. In particular, note:
+//
+// * "$forwarded": The Email has been forwarded.
+//
+// * "$phishing": The Email is highly likely to be phishing.
+// Clients SHOULD warn users to take care when viewing this Email
+// and disable links and attachments.
+//
+// * "$junk": The Email is definitely spam. Clients SHOULD set this
+// flag when users report spam to help train automated spam-
+// detection systems.
+//
+// * "$notjunk": The Email is definitely not spam. Clients SHOULD
+// set this flag when users indicate an Email is legitimate, to
+// help train automated spam-detection systems.
+//
+// o size: "UnsignedInt" (immutable; server-set)
+//
+// The size, in octets, of the raw data for the message [RFC5322] (as
+// referenced by the "blobId", i.e., the number of octets in the file
+// the user would download).
+//
+// o receivedAt: "UTCDate" (immutable; default: time of creation on
+// server)
+//
+// The date the Email was received by the message store. This is the
+// "internal date" in IMAP [RFC3501]./
+
+#[derive(Deserialize, Serialize, Debug)]
+pub struct EmailObject {
+ pub id: Id,
+ pub blob_id: Id,
+ pub thread_id: Id,
+ pub mailbox_ids: HashMap,
+ pub keywords: HashMap,
+ pub size: u64,
+ pub received_at: String,
+}
+
+impl Object for EmailObject {}
+
+#[derive(Deserialize, Serialize, Debug)]
+#[serde(rename_all = "camelCase")]
+pub struct EmailQueryResponse {
+ pub account_id: Id,
+ pub can_calculate_changes: bool,
+ pub collapse_threads: bool,
+ // FIXME
+ pub filter: String,
+ pub ids: Vec,
+ pub position: u64,
+ pub query_state: String,
+ pub sort: Option,
+ pub total: usize,
+}
+
+#[derive(Deserialize, Serialize, Debug)]
+#[serde(rename_all = "camelCase")]
+pub struct EmailQueryCall {
+ pub filter: Vec, /* "inMailboxes": [ folder.id ] },*/
+ pub collapse_threads: bool,
+ pub position: u64,
+ pub fetch_threads: bool,
+ pub fetch_messages: bool,
+ pub fetch_message_properties: Vec,
+}
+
+impl Method for EmailQueryCall {
+ const NAME: &'static str = "Email/query";
+}
+
+#[derive(Deserialize, Serialize, Debug)]
+#[serde(rename_all = "camelCase")]
+pub struct EmailGetCall {
+ pub filter: Vec, /* "inMailboxes": [ folder.id ] },*/
+ pub collapse_threads: bool,
+ pub position: u64,
+ pub fetch_threads: bool,
+ pub fetch_messages: bool,
+ pub fetch_message_properties: Vec,
+}
+
+impl Method for EmailGetCall {
+ const NAME: &'static str = "Email/get";
+}
+
+#[derive(Serialize, Deserialize, Default, Debug)]
+#[serde(rename_all = "camelCase")]
+pub struct EmailFilterCondition {
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ pub in_mailboxes: Vec,
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ pub in_mailbox_other_than: Vec,
+ #[serde(skip_serializing_if = "String::is_empty")]
+ pub before: UtcDate,
+ #[serde(skip_serializing_if = "String::is_empty")]
+ pub after: UtcDate,
+ #[serde(default)]
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub min_size: Option,
+ #[serde(default)]
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub max_size: Option,
+ #[serde(skip_serializing_if = "String::is_empty")]
+ pub all_in_thread_have_keyword: String,
+ #[serde(skip_serializing_if = "String::is_empty")]
+ pub some_in_thread_have_keyword: String,
+ #[serde(skip_serializing_if = "String::is_empty")]
+ pub none_in_thread_have_keyword: String,
+ #[serde(skip_serializing_if = "String::is_empty")]
+ pub has_keyword: String,
+ #[serde(skip_serializing_if = "String::is_empty")]
+ pub not_keyword: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub has_attachment: Option,
+ #[serde(skip_serializing_if = "String::is_empty")]
+ pub text: String,
+ #[serde(skip_serializing_if = "String::is_empty")]
+ pub from: String,
+ #[serde(skip_serializing_if = "String::is_empty")]
+ pub to: String,
+ #[serde(skip_serializing_if = "String::is_empty")]
+ pub cc: String,
+ #[serde(skip_serializing_if = "String::is_empty")]
+ pub bcc: String,
+ #[serde(skip_serializing_if = "String::is_empty")]
+ pub subject: String,
+ #[serde(skip_serializing_if = "String::is_empty")]
+ pub body: String,
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ pub header: Vec,
+}
+
+impl FilterTrait for EmailFilterCondition {}
+
+// The following convenience properties are also specified for the Email
+// object:
+//
+// o messageId: "String[]|null" (immutable)
+//
+// The value is identical to the value of "header:Message-
+// ID:asMessageIds". For messages conforming to RFC 5322, this will
+// be an array with a single entry.
+//
+// o inReplyTo: "String[]|null" (immutable)
+//
+// The value is identical to the value of "header:In-Reply-
+// To:asMessageIds".
+//
+// o references: "String[]|null" (immutable)
+//
+// The value is identical to the value of
+// "header:References:asMessageIds".
+//
+// o sender: "EmailAddress[]|null" (immutable)
+//
+// The value is identical to the value of
+// "header:Sender:asAddresses".
+//
+// o from: "EmailAddress[]|null" (immutable)
+//
+// The value is identical to the value of "header:From:asAddresses".
+//
+// o to: "EmailAddress[]|null" (immutable)
+//
+// The value is identical to the value of "header:To:asAddresses".
+//
+// o cc: "EmailAddress[]|null" (immutable)
+//
+// The value is identical to the value of "header:Cc:asAddresses".
+//
+// o bcc: "EmailAddress[]|null" (immutable)
+//
+// The value is identical to the value of "header:Bcc:asAddresses".
+//
+// o replyTo: "EmailAddress[]|null" (immutable)
+//
+// The value is identical to the value of "header:Reply-
+// To:asAddresses".
+//
+// o subject: "String|null" (immutable)
+//
+// The value is identical to the value of "header:Subject:asText".
+//
+//
+//
+// Jenkins & Newman Standards Track [Page 34]
+//
+// RFC 8621 JMAP Mail August 2019
+//
+//
+// o sentAt: "Date|null" (immutable; default on creation: current
+// server time)
+//
+// The value is identical to the value of "header:Date:asDate".
diff --git a/melib/src/backends/jmap/protocol.rs b/melib/src/backends/jmap/protocol.rs
index 91caf923..125e9366 100644
--- a/melib/src/backends/jmap/protocol.rs
+++ b/melib/src/backends/jmap/protocol.rs
@@ -21,8 +21,34 @@
use super::folder::JmapFolder;
use super::*;
+use serde::{de::DeserializeOwned, Serialize};
use serde_json::{json, Value};
+pub type Id = String;
+pub type UtcDate = String;
+
+use super::rfc8620::Object;
+
+pub trait Method: Serialize {
+ const NAME: &'static str;
+}
+
+#[derive(Deserialize, Serialize, Debug)]
+#[serde(rename_all = "camelCase")]
+pub enum MessageProperty {
+ ThreadId,
+ MailboxId,
+ IsUnread,
+ IsFlagged,
+ IsAnswered,
+ IsDraft,
+ HasAttachment,
+ From,
+ To,
+ Subject,
+ Date,
+ Preview,
+}
macro_rules! get_path_hash {
($path:expr) => {{
use std::collections::hash_map::DefaultHasher;
@@ -35,6 +61,45 @@ macro_rules! get_path_hash {
static USING: &'static [&'static str] = &["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"];
+#[derive(Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct Request {
+ using: &'static [&'static str],
+ /* Why is this Value instead of Box>? The Method trait cannot be made into a
+ * Trait object because its serialize() will be generic. */
+ method_calls: Vec,
+}
+
+impl Request {
+ pub fn new() -> Self {
+ Request {
+ using: USING,
+ method_calls: Vec::new(),
+ }
+ }
+
+ pub fn add_call, O: Object>(&mut self, call: M) {
+ self.method_calls
+ .push(serde_json::to_value((M::NAME, call, "f")).unwrap());
+ }
+}
+
+#[derive(Serialize, Debug)]
+#[serde(untagged)]
+pub enum MethodCall {
+ #[serde(rename_all = "camelCase")]
+ EmailQuery {
+ filter: Vec, /* "inMailboxes": [ folder.id ] },*/
+ collapse_threads: bool,
+ position: u64,
+ fetch_threads: bool,
+ fetch_messages: bool,
+ fetch_message_properties: Vec,
+ },
+ MailboxGet {},
+ Empty {},
+}
+
pub fn get_mailboxes(conn: &mut JmapConnection) -> Result> {
let res = conn
.client
@@ -195,6 +260,74 @@ pub struct JmapRights {
// }, "call1"]
// ]
pub fn get_message_list(conn: &mut JmapConnection, folder: &JmapFolder) -> Result> {
+ let email_call: EmailQueryCall = EmailQueryCall {
+ filter: vec![EmailFilterCondition {
+ in_mailboxes: vec![folder.id.clone()],
+ ..Default::default()
+ }],
+ collapse_threads: false,
+ position: 0,
+ fetch_threads: true,
+ fetch_messages: true,
+ fetch_message_properties: vec![
+ MessageProperty::ThreadId,
+ MessageProperty::MailboxId,
+ MessageProperty::IsUnread,
+ MessageProperty::IsFlagged,
+ MessageProperty::IsAnswered,
+ MessageProperty::IsDraft,
+ MessageProperty::HasAttachment,
+ MessageProperty::From,
+ MessageProperty::To,
+ MessageProperty::Subject,
+ MessageProperty::Date,
+ MessageProperty::Preview,
+ ],
+ };
+
+ let mut req = Request::new();
+ req.add_call(email_call);
+ std::dbg!(serde_json::to_string(&req));
+
+ /*
+ {
+ "using": [
+ "urn:ietf:params:jmap:core",
+ "urn:ietf:params:jmap:mail"
+ ],
+ "methodCalls": [[
+ "Email/query",
+ {
+ "collapseThreads": false,
+ "fetchMessageProperties": [
+ "threadId",
+ "mailboxId",
+ "isUnread",
+ "isFlagged",
+ "isAnswered",
+ "isDraft",
+ "hasAttachment",
+ "from",
+ "to",
+ "subject",
+ "date",
+ "preview"
+ ],
+ "fetchMessages": true,
+ "fetchThreads": true,
+ "filter": [
+ {
+ "inMailboxes": [
+ "fde49e47-14e7-11ea-a277-2477037a1804"
+ ]
+ }
+ ],
+ "position": 0
+ },
+ "f"
+ ]]
+ }
+ */
let res = conn
.client
.post("https://jmap-proxy.local/jmap/fc32dffe-14e7-11ea-a277-2477037a1804/")
diff --git a/melib/src/backends/jmap/rfc8620.rs b/melib/src/backends/jmap/rfc8620.rs
new file mode 100644
index 00000000..55863b6c
--- /dev/null
+++ b/melib/src/backends/jmap/rfc8620.rs
@@ -0,0 +1,408 @@
+/*
+ * 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 .
+ */
+
+use core::marker::PhantomData;
+use serde::{de::DeserializeOwned, Serialize};
+mod filters;
+pub use filters::*;
+mod comparator;
+pub use comparator::*;
+
+use super::protocol::Method;
+pub trait Object {}
+
+// 5.1. /get
+//
+// Objects of type Foo are fetched via a call to "Foo/get".
+//
+// It takes the following arguments:
+//
+// o accountId: "Id"
+//
+// The id of the account to use.
+//
+// o ids: "Id[]|null"
+//
+// The ids of the Foo objects to return. If null, then *all* records
+// of the data type are returned, if this is supported for that data
+// type and the number of records does not exceed the
+// "maxObjectsInGet" limit.
+//
+// o properties: "String[]|null"
+//
+// If supplied, only the properties listed in the array are returned
+// for each Foo object. If null, all properties of the object are
+// returned. The id property of the object is *always* returned,
+// even if not explicitly requested. If an invalid property is
+// requested, the call MUST be rejected with an "invalidArguments"
+// error.
+
+#[derive(Deserialize, Serialize, Debug)]
+#[serde(rename_all = "camelCase")]
+pub struct GetCall>
+where
+ OBJ: std::fmt::Debug + Serialize,
+{
+ #[serde(skip_serializing_if = "String::is_empty")]
+ account_id: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ ids: Option>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ properties: Option>,
+ _ph: PhantomData<*const CALL>,
+ __ph: PhantomData<*const OBJ>,
+}
+
+// The response has the following arguments:
+//
+// o accountId: "Id"
+//
+// The id of the account used for the call.
+//
+// o state: "String"
+//
+// A (preferably short) string representing the state on the server
+// for *all* the data of this type in the account (not just the
+// objects returned in this call). If the data changes, this string
+// MUST change. If the Foo data is unchanged, servers SHOULD return
+// the same state string on subsequent requests for this data type.
+// When a client receives a response with a different state string to
+// a previous call, it MUST either throw away all currently cached
+// objects for the type or call "Foo/changes" to get the exact
+// changes.
+//
+// o list: "Foo[]"
+//
+// An array of the Foo objects requested. This is the *empty array*
+// if no objects were found or if the "ids" argument passed in was
+// also an empty array. The results MAY be in a different order to
+// the "ids" in the request arguments. If an identical id is
+// included more than once in the request, the server MUST only
+// include it once in either the "list" or the "notFound" argument of
+// the response.
+//
+// o notFound: "Id[]"
+//
+// This array contains the ids passed to the method for records that
+// do not exist. The array is empty if all requested ids were found
+// or if the "ids" argument passed in was either null or an empty
+// array.
+//
+// The following additional error may be returned instead of the "Foo/
+// get" response:
+//
+// "requestTooLarge": The number of ids requested by the client exceeds
+// the maximum number the server is willing to process in a single
+// method call.
+
+#[derive(Serialize, Debug)]
+#[serde(rename_all = "camelCase")]
+pub struct GetResponse {
+ #[serde(skip_serializing_if = "String::is_empty")]
+ account_id: String,
+ state: String,
+ list: Vec,
+ not_found: Vec,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+enum JmapError {
+ RequestTooLarge,
+ InvalidArguments,
+}
+
+// 5.5. /query
+//
+// For data sets where the total amount of data is expected to be very
+// small, clients can just fetch the complete set of data and then do
+// any sorting/filtering locally. However, for large data sets (e.g.,
+// multi-gigabyte mailboxes), the client needs to be able to
+// search/sort/window the data type on the server.
+//
+// A query on the set of Foos in an account is made by calling "Foo/
+// query". This takes a number of arguments to determine which records
+// to include, how they should be sorted, and which part of the result
+// should be returned (the full list may be *very* long). The result is
+// returned as a list of Foo ids.
+//
+// A call to "Foo/query" takes the following arguments:
+//
+// o accountId: "Id"
+//
+// The id of the account to use.
+//
+// o filter: "FilterOperator|FilterCondition|null"
+//
+// Determines the set of Foos returned in the results. If null, all
+// objects in the account of this type are included in the results.
+// A *FilterOperator* object has the following properties:
+//
+// * operator: "String"
+//
+// This MUST be one of the following strings:
+//
+// + "AND": All of the conditions must match for the filter to
+// match.
+//
+// + "OR": At least one of the conditions must match for the
+// filter to match.
+//
+// + "NOT": None of the conditions must match for the filter to
+// match.
+//
+// * conditions: "(FilterOperator|FilterCondition)[]"
+//
+// The conditions to evaluate against each record.
+//
+// A *FilterCondition* is an "object" whose allowed properties and
+// semantics depend on the data type and is defined in the /query
+// method specification for that type. It MUST NOT have an
+// "operator" property.
+//
+// o sort: "Comparator[]|null"
+//
+// Lists the names of properties to compare between two Foo records,
+// and how to compare them, to determine which comes first in the
+// sort. If two Foo records have an identical value for the first
+// comparator, the next comparator will be considered, and so on. If
+// all comparators are the same (this includes the case where an
+// empty array or null is given as the "sort" argument), the sort
+// order is server dependent, but it MUST be stable between calls to
+// "Foo/query". A *Comparator* has the following properties:
+//
+// * property: "String"
+//
+// The name of the property on the Foo objects to compare.
+//
+// * isAscending: "Boolean" (optional; default: true)
+//
+// If true, sort in ascending order. If false, reverse the
+// comparator's results to sort in descending order.
+//
+// * collation: "String" (optional; default is server-dependent)
+//
+// The identifier, as registered in the collation registry defined
+// in [RFC4790], for the algorithm to use when comparing the order
+// of strings. The algorithms the server supports are advertised
+// in the capabilities object returned with the Session object
+// (see Section 2).
+//
+// If omitted, the default algorithm is server dependent, but:
+//
+// 1. It MUST be unicode-aware.
+//
+// 2. It MAY be selected based on an Accept-Language header in
+// the request (as defined in [RFC7231], Section 5.3.5) or
+// out-of-band information about the user's language/locale.
+//
+// 3. It SHOULD be case insensitive where such a concept makes
+// sense for a language/locale. Where the user's language is
+// unknown, it is RECOMMENDED to follow the advice in
+// Section 5.2.3 of [RFC8264].
+//
+// The "i;unicode-casemap" collation [RFC5051] and the Unicode
+// Collation Algorithm ()
+// are two examples that fulfil these criterion and provide
+// reasonable behaviour for a large number of languages.
+//
+// When the property being compared is not a string, the
+// "collation" property is ignored, and the following comparison
+// rules apply based on the type. In ascending order:
+//
+// + "Boolean": false comes before true.
+//
+// + "Number": A lower number comes before a higher number.
+//
+// + "Date"/"UTCDate": The earlier date comes first.
+//
+// The Comparator object may also have additional properties as
+// required for specific sort operations defined in a type's /query
+// method.
+//
+// o position: "Int" (default: 0)
+//
+// The zero-based index of the first id in the full list of results
+// to return.
+//
+// If a negative value is given, it is an offset from the end of the
+// list. Specifically, the negative value MUST be added to the total
+// number of results given the filter, and if still negative, it's
+// clamped to "0". This is now the zero-based index of the first id
+// to return.
+//
+// If the index is greater than or equal to the total number of
+// objects in the results list, then the "ids" array in the response
+// will be empty, but this is not an error.
+//
+// o anchor: "Id|null"
+//
+// A Foo id. If supplied, the "position" argument is ignored. The
+// index of this id in the results will be used in combination with
+// the "anchorOffset" argument to determine the index of the first
+// result to return (see below for more details).
+//
+// o anchorOffset: "Int" (default: 0)
+//
+// The index of the first result to return relative to the index of
+// the anchor, if an anchor is given. This MAY be negative. For
+// example, "-1" means the Foo immediately preceding the anchor is
+// the first result in the list returned (see below for more
+// details).
+//
+// o limit: "UnsignedInt|null"
+//
+// The maximum number of results to return. If null, no limit
+// presumed. The server MAY choose to enforce a maximum "limit"
+// argument. In this case, if a greater value is given (or if it is
+// null), the limit is clamped to the maximum; the new limit is
+// returned with the response so the client is aware. If a negative
+// value is given, the call MUST be rejected with an
+// "invalidArguments" error.
+//
+// o calculateTotal: "Boolean" (default: false)
+//
+// Does the client wish to know the total number of results in the
+// query? This may be slow and expensive for servers to calculate,
+// particularly with complex filters, so clients should take care to
+// only request the total when needed.
+//
+// If an "anchor" argument is given, the anchor is looked for in the
+// results after filtering and sorting. If found, the "anchorOffset" is
+// then added to its index. If the resulting index is now negative, it
+// is clamped to 0. This index is now used exactly as though it were
+// supplied as the "position" argument. If the anchor is not found, the
+// call is rejected with an "anchorNotFound" error.
+//
+// If an "anchor" is specified, any position argument supplied by the
+// client MUST be ignored. If no "anchor" is supplied, any
+// "anchorOffset" argument MUST be ignored.
+//
+// A client can use "anchor" instead of "position" to find the index of
+// an id within a large set of results.
+
+#[derive(Serialize, Debug)]
+#[serde(rename_all = "camelCase")]
+pub struct QueryCall, OBJ: Object>
+where
+ OBJ: std::fmt::Debug + Serialize,
+{
+ account_id: String,
+ filter: Option>,
+ sort: Option>,
+ #[serde(default)]
+ position: u64,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ anchor: Option,
+ #[serde(default)]
+ anchor_offset: u64,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ limit: Option,
+ #[serde(default = "bool_false")]
+ calculate_total: bool,
+ _ph: PhantomData<*const OBJ>,
+}
+
+fn bool_false() -> bool {
+ false
+}
+
+fn bool_true() -> bool {
+ true
+}
+
+// The response has the following arguments:
+//
+// o accountId: "Id"
+//
+// The id of the account used for the call.
+//
+// o queryState: "String"
+//
+// A string encoding the current state of the query on the server.
+// This string MUST change if the results of the query (i.e., the
+// matching ids and their sort order) have changed. The queryState
+// string MAY change if something has changed on the server, which
+// means the results may have changed but the server doesn't know for
+// sure.
+//
+// The queryState string only represents the ordered list of ids that
+// match the particular query (including its sort/filter). There is
+// no requirement for it to change if a property on an object
+// matching the query changes but the query results are unaffected
+// (indeed, it is more efficient if the queryState string does not
+// change in this case). The queryState string only has meaning when
+// compared to future responses to a query with the same type/sort/
+// filter or when used with /queryChanges to fetch changes.
+//
+// Should a client receive back a response with a different
+// queryState string to a previous call, it MUST either throw away
+// the currently cached query and fetch it again (note, this does not
+// require fetching the records again, just the list of ids) or call
+// "Foo/queryChanges" to get the difference.
+//
+// o canCalculateChanges: "Boolean"
+//
+// This is true if the server supports calling "Foo/queryChanges"
+// with these "filter"/"sort" parameters. Note, this does not
+// guarantee that the "Foo/queryChanges" call will succeed, as it may
+// only be possible for a limited time afterwards due to server
+// internal implementation details.
+//
+// o position: "UnsignedInt"
+//
+// The zero-based index of the first result in the "ids" array within
+// the complete list of query results.
+//
+// o ids: "Id[]"
+//
+// The list of ids for each Foo in the query results, starting at the
+// index given by the "position" argument of this response and
+// continuing until it hits the end of the results or reaches the
+// "limit" number of ids. If "position" is >= "total", this MUST be
+// the empty list.
+//
+// o total: "UnsignedInt" (only if requested)
+//
+// The total number of Foos in the results (given the "filter").
+// This argument MUST be omitted if the "calculateTotal" request
+// argument is not true.
+//
+// o limit: "UnsignedInt" (if set by the server)
+//
+// The limit enforced by the server on the maximum number of results
+// to return. This is only returned if the server set a limit or
+// used a different limit than that given in the request.
+//
+// The following additional errors may be returned instead of the "Foo/
+// query" response:
+//
+// "anchorNotFound": An anchor argument was supplied, but it cannot be
+// found in the results of the query.
+//
+// "unsupportedSort": The "sort" is syntactically valid, but it includes
+// a property the server does not support sorting on or a collation
+// method it does not recognise.
+//
+// "unsupportedFilter": The "filter" is syntactically valid, but the
+// server cannot process it. If the filter was the result of a user's
+// search input, the client SHOULD suggest that the user simplify their
+// search.
diff --git a/melib/src/backends/jmap/rfc8620/comparator.rs b/melib/src/backends/jmap/rfc8620/comparator.rs
new file mode 100644
index 00000000..c11ad266
--- /dev/null
+++ b/melib/src/backends/jmap/rfc8620/comparator.rs
@@ -0,0 +1,44 @@
+/*
+ * 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 .
+ */
+
+use super::*;
+
+#[derive(Serialize, Debug)]
+#[serde(rename_all = "camelCase")]
+pub struct Comparator {
+ property: String,
+ #[serde(default = "bool_true")]
+ is_ascending: bool,
+ //FIXME
+ collation: Option,
+ //#[serde(flatten)]
+ additional_properties: Vec,
+
+ _ph: PhantomData<*const OBJ>,
+}
+
+#[derive(Serialize, Debug)]
+#[serde(rename_all = "UPPERCASE")]
+pub enum FilterOperator {
+ And,
+ Or,
+ Not,
+}
diff --git a/melib/src/backends/jmap/rfc8620/filters.rs b/melib/src/backends/jmap/rfc8620/filters.rs
new file mode 100644
index 00000000..61697d0d
--- /dev/null
+++ b/melib/src/backends/jmap/rfc8620/filters.rs
@@ -0,0 +1,50 @@
+/*
+ * 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 .
+ */
+
+use super::*;
+
+pub trait FilterTrait {}
+#[derive(Serialize, Debug)]
+#[serde(rename_all = "camelCase")]
+#[serde(untagged)]
+pub enum Filter, OBJ: Object> {
+ Operator {
+ operator: FilterOperator,
+ conditions: Vec>,
+ },
+ Condition(FilterCondition),
+}
+
+#[derive(Serialize, Debug)]
+pub struct FilterCondition, OBJ: Object> {
+ #[serde(flatten)]
+ cond: F,
+ #[serde(skip)]
+ _ph: PhantomData<*const OBJ>,
+}
+
+#[derive(Serialize, Debug)]
+#[serde(rename_all = "UPPERCASE")]
+pub enum FilterOperator {
+ And,
+ Or,
+ Not,
+}