//! 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. use std::{borrow::Cow, collections::HashMap}; use serde::{Deserialize, Serialize}; use serde_json::Value; use crate::common::{Id, Int, UnsignedInt}; #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] pub struct QueryParams<'a> { /// The id of the account to use. #[serde(borrow)] account_id: Id<'a>, /// Determines the set of Foos returned in the results. If null, all /// objects in the account of this type are included in the results. filter: Filter<'a>, /// 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". #[serde(default)] sort: Vec>, /// Offset into the list of results to return. #[serde(default, flatten)] offset: Offset<'a>, /// 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. limit: Option, /// 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. #[serde(default)] calculate_total: bool, } #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] pub struct QueryResponse<'a> { /// The id of the account used for the call. #[serde(borrow)] account_id: Id<'a>, /// 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. query_state: QueryState<'a>, /// 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. can_calculate_changes: bool, /// The zero-based index of the first result in the "ids" array within /// the complete list of query results. position: UnsignedInt, /// 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. ids: Vec>, /// The total number of Foos in the results (given the "filter"). /// This argument MUST be omitted if the "calculateTotal" request /// argument is not true. total: Option, /// 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. limit: Option, } /// 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. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct QueryState<'a>(#[serde(borrow)] Cow<'a, str>); #[derive(Serialize, Deserialize, Debug, Clone, Default)] #[serde(untagged)] pub enum Offset<'a> { Position { /// 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. position: Int, }, Anchor { /// 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). #[serde(borrow)] anchor: Id<'a>, /// 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). #[serde(default)] anchor_offset: Int, }, #[default] Default, } #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] pub struct Comparator<'a> { /// The name of the property on the Foo objects to compare. property: Cow<'a, str>, /// If true, sort in ascending order. If false, reverse the /// comparator's results to sort in descending order. #[serde(default = "default_is_ascending")] is_ascending: bool, /// 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). collation: Option>, } const fn default_is_ascending() -> bool { true } #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(untagged)] pub enum Filter<'a> { Operator(FilterOperator<'a>), Condition(HashMap, Value>), } /// 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. #[derive(Serialize, Deserialize, Debug, Clone)] pub struct FilterCondition<'a>(HashMap, Value>); #[derive(Serialize, Deserialize, Debug, Clone)] pub struct FilterOperator<'a> { operator: Operator, conditions: Vec>, } #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum Operator { /// All of the conditions must match for the filter to match. And, /// At least one of the conditions must match for the /// filter to match. Or, /// None of the conditions must match for the filter to /// match. Not, }