//! 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<Comparator<'a>>,
/// 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<UnsignedInt>,
/// 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<Id<'a>>,
/// 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<UnsignedInt>,
/// 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<UnsignedInt>,
}
/// 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<Cow<'a, str>>,
}
const fn default_is_ascending() -> bool {
true
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum Filter<'a> {
Operator(FilterOperator<'a>),
Condition(HashMap<Cow<'a, str>, 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<Cow<'a, str>, Value>);
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct FilterOperator<'a> {
operator: Operator,
conditions: Vec<Filter<'a>>,
}
#[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,
}