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, +}