use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use serde_json::{json, Map, Value};
use crate::client::Client;
use crate::error::{Error, Result};
use crate::response::Response;
pub type Document = Map<String, Value>;
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(transparent)]
pub struct DocumentFilter(Value);
fn field_filter(variant: &str, field: &str, value: impl Into<Value>) -> DocumentFilter {
DocumentFilter(json!({variant: {"field": field, "value": value.into()}}))
}
impl DocumentFilter {
pub fn all() -> Self {
DocumentFilter(Value::String("All".into()))
}
pub fn eq(field: &str, value: impl Into<Value>) -> Self {
field_filter("Eq", field, value)
}
pub fn ne(field: &str, value: impl Into<Value>) -> Self {
field_filter("Ne", field, value)
}
pub fn gt(field: &str, value: impl Into<Value>) -> Self {
field_filter("Gt", field, value)
}
pub fn gte(field: &str, value: impl Into<Value>) -> Self {
field_filter("Gte", field, value)
}
pub fn lt(field: &str, value: impl Into<Value>) -> Self {
field_filter("Lt", field, value)
}
pub fn lte(field: &str, value: impl Into<Value>) -> Self {
field_filter("Lte", field, value)
}
pub fn contains(field: &str, value: impl Into<Value>) -> Self {
field_filter("Contains", field, value)
}
pub fn is_in<V: Into<Value>>(field: &str, values: impl IntoIterator<Item = V>) -> Self {
let values: Vec<Value> = values.into_iter().map(Into::into).collect();
DocumentFilter(json!({"In": {"field": field, "values": values}}))
}
pub fn and(filters: impl IntoIterator<Item = DocumentFilter>) -> Self {
let filters: Vec<Value> = filters.into_iter().map(|f| f.0).collect();
DocumentFilter(json!({"And": filters}))
}
}
#[derive(Debug, Clone, Default, PartialEq, Serialize)]
pub struct DocumentUpdate {
#[serde(skip_serializing_if = "Map::is_empty")]
pub set: Map<String, Value>,
#[serde(skip_serializing_if = "Map::is_empty")]
pub inc: Map<String, Value>,
}
impl DocumentUpdate {
pub fn new() -> Self {
Self::default()
}
pub fn set(mut self, path: &str, value: impl Into<Value>) -> Self {
self.set.insert(path.to_string(), value.into());
self
}
pub fn inc(mut self, path: &str, delta: impl Into<Value>) -> Self {
self.inc.insert(path.to_string(), delta.into());
self
}
fn is_empty(&self) -> bool {
self.set.is_empty() && self.inc.is_empty()
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(transparent)]
pub struct GroupKey(Value);
impl GroupKey {
pub fn field(path: &str) -> Self {
GroupKey(json!({"Field": path}))
}
pub fn constant(value: impl Into<Value>) -> Self {
GroupKey(json!({"Constant": value.into()}))
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(transparent)]
pub struct AccumulatorOp(Value);
impl AccumulatorOp {
pub fn sum(field: &str) -> Self {
AccumulatorOp(json!({"Sum": field}))
}
pub fn avg(field: &str) -> Self {
AccumulatorOp(json!({"Avg": field}))
}
pub fn min(field: &str) -> Self {
AccumulatorOp(json!({"Min": field}))
}
pub fn max(field: &str) -> Self {
AccumulatorOp(json!({"Max": field}))
}
pub fn count() -> Self {
AccumulatorOp(Value::String("Count".into()))
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct GroupAccumulator {
pub output: String,
pub op: AccumulatorOp,
}
impl GroupAccumulator {
pub fn new(output: &str, op: AccumulatorOp) -> Self {
Self {
output: output.to_string(),
op,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct SortKey {
pub field: String,
pub descending: bool,
}
impl SortKey {
pub fn ascending(field: &str) -> Self {
Self {
field: field.to_string(),
descending: false,
}
}
pub fn descending(field: &str) -> Self {
Self {
field: field.to_string(),
descending: true,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(transparent)]
pub struct AggregateStage(Value);
impl AggregateStage {
pub fn filter(filter: DocumentFilter) -> Self {
AggregateStage(json!({"Match": filter}))
}
pub fn group(by: GroupKey, accumulators: impl IntoIterator<Item = GroupAccumulator>) -> Self {
let accumulators: Vec<GroupAccumulator> = accumulators.into_iter().collect();
AggregateStage(json!({"Group": {"by": by, "accumulators": accumulators}}))
}
pub fn sort(keys: impl IntoIterator<Item = SortKey>) -> Self {
let keys: Vec<SortKey> = keys.into_iter().collect();
AggregateStage(json!({"Sort": keys}))
}
pub fn skip(n: usize) -> Self {
AggregateStage(json!({"Skip": n}))
}
pub fn limit(n: usize) -> Self {
AggregateStage(json!({"Limit": n}))
}
pub fn project(fields: impl IntoIterator<Item = impl Into<String>>, include: bool) -> Self {
let fields: Vec<String> = fields.into_iter().map(Into::into).collect();
AggregateStage(json!({"Project": {"fields": fields, "include": include}}))
}
pub fn count(field: &str) -> Self {
AggregateStage(json!({"Count": {"field": field}}))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct DocumentIndex {
#[serde(rename = "index_name")]
pub name: String,
#[serde(default)]
pub field: String,
#[serde(default)]
pub unique: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct DocumentStats {
#[serde(rename = "analyzed")]
pub collection: String,
#[serde(default)]
pub document_count: i64,
#[serde(default)]
pub indexed_fields: i64,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)]
pub struct UpdateCounts {
#[serde(default)]
pub matched: i64,
#[serde(default)]
pub modified: i64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize)]
pub enum ReturnDocument {
#[default]
None,
Before,
After,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct ReplaceOptions {
pub upsert: bool,
pub return_document: ReturnDocument,
pub expected_version: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct ReplaceResult {
#[serde(default)]
pub id: String,
#[serde(default)]
pub replaced: bool,
#[serde(default)]
pub inserted: bool,
#[serde(default)]
pub document: Option<Document>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)]
pub struct PurgeCounts {
#[serde(default)]
pub purged: i64,
#[serde(default)]
pub more: bool,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct ExplainOptions {
pub limit: Option<usize>,
pub sort: Vec<SortKey>,
pub skip: Option<usize>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct DocumentPlan {
#[serde(default)]
pub plan: String,
#[serde(default)]
pub index: Option<String>,
#[serde(default)]
pub index_used: bool,
#[serde(default)]
pub scan_reason: Option<String>,
#[serde(default)]
pub scan_source: Option<String>,
#[serde(default)]
pub estimated_rows: f64,
#[serde(default)]
pub estimated_cost: f64,
#[serde(default)]
pub rows_examined: i64,
#[serde(default)]
pub rows_returned: i64,
#[serde(default)]
pub elapsed_us: i64,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct IndexRebuild {
#[serde(rename = "rebuilt")]
pub index_name: String,
#[serde(default)]
pub documents: i64,
#[serde(default)]
pub entries: i64,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)]
pub struct DocumentCommit {
#[serde(default)]
pub committed: bool,
#[serde(default)]
pub writes: i64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FieldType {
String,
Number,
Integer,
Boolean,
Object,
Array,
Null,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct FieldRule {
#[serde(default, rename = "type", skip_serializing_if = "Option::is_none")]
pub field_type: Option<FieldType>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub one_of: Vec<Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub minimum: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub maximum: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub min_length: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_length: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub pattern: Option<String>,
}
impl FieldRule {
pub fn of_type(field_type: FieldType) -> Self {
Self {
field_type: Some(field_type),
..Self::default()
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DocumentSchema {
#[serde(default)]
pub required: Vec<String>,
#[serde(default)]
pub fields: BTreeMap<String, FieldRule>,
#[serde(default = "yes")]
pub additional_fields: bool,
}
impl Default for DocumentSchema {
fn default() -> Self {
Self {
required: Vec::new(),
fields: BTreeMap::new(),
additional_fields: true,
}
}
}
fn yes() -> bool {
true
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct CollectionOptions {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub schema: Option<DocumentSchema>,
#[serde(default, skip_serializing_if = "Map::is_empty")]
pub defaults: Map<String, Value>,
#[serde(default)]
pub versioned: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub enum BulkOp {
Insert {
id: Option<String>,
document: Value,
},
UpdateOne {
id: String,
update: DocumentUpdate,
upsert: bool,
expected_version: Option<u64>,
},
Replace {
id: String,
document: Value,
upsert: bool,
expected_version: Option<u64>,
},
Delete {
id: String,
expected_version: Option<u64>,
},
}
impl BulkOp {
pub fn insert(document: impl Into<Value>) -> Self {
BulkOp::Insert {
id: None,
document: document.into(),
}
}
pub fn insert_with_id(id: &str, document: impl Into<Value>) -> Self {
BulkOp::Insert {
id: Some(id.to_string()),
document: document.into(),
}
}
pub fn update_one(id: &str, update: DocumentUpdate) -> Self {
BulkOp::UpdateOne {
id: id.to_string(),
update,
upsert: false,
expected_version: None,
}
}
pub fn replace(id: &str, document: impl Into<Value>) -> Self {
BulkOp::Replace {
id: id.to_string(),
document: document.into(),
upsert: false,
expected_version: None,
}
}
pub fn delete(id: &str) -> Self {
BulkOp::Delete {
id: id.to_string(),
expected_version: None,
}
}
pub fn upsert(mut self) -> Self {
if let BulkOp::UpdateOne { upsert, .. } | BulkOp::Replace { upsert, .. } = &mut self {
*upsert = true;
}
self
}
pub fn expected_version(mut self, version: u64) -> Self {
match &mut self {
BulkOp::UpdateOne {
expected_version, ..
}
| BulkOp::Replace {
expected_version, ..
}
| BulkOp::Delete {
expected_version, ..
} => *expected_version = Some(version),
BulkOp::Insert { .. } => {}
}
self
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize)]
pub struct BulkWriteResult {
#[serde(default)]
pub inserted: i64,
#[serde(default)]
pub updated: i64,
#[serde(default)]
pub upserted: i64,
#[serde(default)]
pub replaced: i64,
#[serde(default)]
pub deleted: i64,
#[serde(default)]
pub inserted_ids: Vec<String>,
}
fn op(body: Value) -> Value {
json!({"Document": body})
}
impl Client {
pub fn document_create_collection(&mut self, collection: &str) -> Result<()> {
self.send(op(json!({"CreateCollection": {"collection": collection}})))
.map(|_| ())
}
pub fn document_drop_collection(&mut self, collection: &str) -> Result<()> {
self.send(op(json!({"DropCollection": {"collection": collection}})))
.map(|_| ())
}
pub fn document_list_collections(&mut self) -> Result<Vec<String>> {
#[derive(Deserialize)]
struct Collections {
#[serde(default)]
collections: Vec<String>,
}
let response = self.send(op(Value::String("ListCollections".into())))?;
let out: Collections = response.decode("ListCollections")?;
Ok(out.collections)
}
pub fn document_insert(
&mut self,
collection: &str,
document: &impl Serialize,
) -> Result<String> {
self.document_insert_inner(collection, Value::Null, document)
}
pub fn document_insert_with_id(
&mut self,
collection: &str,
id: &str,
document: &impl Serialize,
) -> Result<String> {
self.document_insert_inner(collection, json!(id), document)
}
fn document_insert_inner(
&mut self,
collection: &str,
id: Value,
document: &impl Serialize,
) -> Result<String> {
#[derive(Deserialize)]
struct Inserted {
#[serde(default)]
id: String,
}
let document = serde_json::to_value(document)
.map_err(|e| Error::invalid(format!("the document cannot be encoded as JSON: {e}")))?;
if !document.is_object() {
return Err(Error::invalid(
"a document must encode to a JSON object, not a scalar or an array",
));
}
let response = self.send(op(json!({"Insert": {
"collection": collection,
"id": id,
"document": document,
}})))?;
let out: Inserted = response.decode("Insert")?;
if out.id.is_empty() {
return Err(Error::protocol("Insert answered with no document id"));
}
Ok(out.id)
}
pub fn document_get(&mut self, collection: &str, id: &str) -> Result<Option<Document>> {
let response = self.send(op(json!({"Get": {"collection": collection, "id": id}})))?;
Ok(documents_of(&response, "Get")?.into_iter().next())
}
pub fn document_find(
&mut self,
collection: &str,
filter: &DocumentFilter,
) -> Result<Vec<Document>> {
self.document_find_inner(collection, filter, Value::Null)
}
pub fn document_find_limit(
&mut self,
collection: &str,
filter: &DocumentFilter,
limit: usize,
) -> Result<Vec<Document>> {
let limit = if limit == 0 {
Value::Null
} else {
json!(limit)
};
self.document_find_inner(collection, filter, limit)
}
fn document_find_inner(
&mut self,
collection: &str,
filter: &DocumentFilter,
limit: Value,
) -> Result<Vec<Document>> {
let response = self.send(op(json!({"Find": {
"collection": collection,
"filter": filter,
"limit": limit,
}})))?;
documents_of(&response, "Find")
}
pub fn document_update(
&mut self,
collection: &str,
id: &str,
set: &Map<String, Value>,
) -> Result<()> {
self.send(op(json!({"Update": {
"collection": collection,
"id": id,
"set": set,
}})))
.map(|_| ())
}
pub fn document_update_one(
&mut self,
collection: &str,
id: &str,
update: &DocumentUpdate,
) -> Result<()> {
self.update_one(collection, id, update, false).map(|_| ())
}
pub fn document_upsert_one(
&mut self,
collection: &str,
id: &str,
update: &DocumentUpdate,
) -> Result<bool> {
let out = self.update_one(collection, id, update, true)?;
Ok(out.inserted)
}
fn update_one(
&mut self,
collection: &str,
id: &str,
update: &DocumentUpdate,
upsert: bool,
) -> Result<UpdateOne> {
if update.is_empty() {
return Err(Error::invalid(
"an update must set or increment at least one field",
));
}
let response = self.send(op(json!({"UpdateOne": {
"collection": collection,
"id": id,
"update": update,
"upsert": upsert,
}})))?;
response.decode("UpdateOne")
}
pub fn document_update_many(
&mut self,
collection: &str,
filter: &DocumentFilter,
update: &DocumentUpdate,
) -> Result<UpdateCounts> {
if update.is_empty() {
return Err(Error::invalid(
"an update must set or increment at least one field",
));
}
let response = self.send(op(json!({"UpdateMany": {
"collection": collection,
"filter": filter,
"update": update,
}})))?;
response.decode("UpdateMany")
}
pub fn document_delete(&mut self, collection: &str, id: &str) -> Result<()> {
self.send(op(json!({"Delete": {"collection": collection, "id": id}})))
.map(|_| ())
}
pub fn document_create_index(
&mut self,
collection: &str,
index_name: &str,
field: &str,
unique: bool,
) -> Result<()> {
self.send(op(json!({"CreateIndex": {
"collection": collection,
"index_name": index_name,
"field": field,
"unique": unique,
}})))
.map(|_| ())
}
pub fn document_drop_index(&mut self, collection: &str, index_name: &str) -> Result<()> {
self.send(op(json!({"DropIndex": {
"collection": collection,
"index_name": index_name,
}})))
.map(|_| ())
}
pub fn document_list_indexes(&mut self, collection: &str) -> Result<Vec<DocumentIndex>> {
#[derive(Deserialize)]
struct Indexes {
#[serde(default)]
indexes: Vec<DocumentIndex>,
}
let response = self.send(op(json!({"ListIndexes": {"collection": collection}})))?;
let out: Indexes = response.decode("ListIndexes")?;
Ok(out.indexes)
}
pub fn document_analyze(&mut self, collection: &str) -> Result<DocumentStats> {
let response = self.send(op(json!({"Analyze": {"collection": collection}})))?;
response.decode("Analyze")
}
pub fn document_aggregate(
&mut self,
collection: &str,
pipeline: &[AggregateStage],
) -> Result<Vec<Document>> {
let response = self.send(op(json!({"Aggregate": {
"collection": collection,
"pipeline": pipeline,
}})))?;
documents_of(&response, "Aggregate")
}
pub fn document_replace(
&mut self,
collection: &str,
id: &str,
document: &impl Serialize,
options: &ReplaceOptions,
) -> Result<ReplaceResult> {
let mut body = Map::new();
body.insert("collection".into(), json!(collection));
body.insert("id".into(), json!(id));
body.insert("document".into(), object_value(document)?);
body.insert("upsert".into(), json!(options.upsert));
if options.return_document != ReturnDocument::None {
body.insert("return".into(), json!(options.return_document));
}
if let Some(version) = options.expected_version {
body.insert("expected_version".into(), json!(version));
}
let response = self.send(op(json!({"Replace": Value::Object(body)})))?;
response.decode("Replace")
}
pub fn document_insert_many(
&mut self,
collection: &str,
documents: &[impl Serialize],
) -> Result<Vec<String>> {
#[derive(Deserialize)]
struct Inserted {
#[serde(default)]
ids: Vec<String>,
}
let documents = documents
.iter()
.map(object_value)
.collect::<Result<Vec<Value>>>()?;
let response = self.send(op(json!({"InsertMany": {
"collection": collection,
"documents": documents,
}})))?;
let out: Inserted = response.decode("InsertMany")?;
Ok(out.ids)
}
pub fn document_delete_many(
&mut self,
collection: &str,
filter: &DocumentFilter,
) -> Result<i64> {
#[derive(Deserialize)]
struct Deleted {
#[serde(default)]
deleted: i64,
}
let response = self.send(op(json!({"DeleteMany": {
"collection": collection,
"filter": filter,
}})))?;
let out: Deleted = response.decode("DeleteMany")?;
Ok(out.deleted)
}
pub fn document_rename_collection(&mut self, collection: &str, to: &str) -> Result<()> {
self.send(op(json!({"RenameCollection": {
"collection": collection,
"to": to,
}})))
.map(|_| ())
}
pub fn document_purge_expired(&mut self, collection: &str) -> Result<PurgeCounts> {
let response = self.send(op(json!({"PurgeExpired": {"collection": collection}})))?;
response.decode("PurgeExpired")
}
pub fn document_explain(
&mut self,
collection: &str,
filter: &DocumentFilter,
options: &ExplainOptions,
) -> Result<DocumentPlan> {
let mut body = Map::new();
body.insert("collection".into(), json!(collection));
body.insert("filter".into(), json!(filter));
if let Some(limit) = options.limit {
body.insert("limit".into(), json!(limit));
}
if !options.sort.is_empty() {
body.insert("sort".into(), json!(options.sort));
}
if let Some(skip) = options.skip {
body.insert("skip".into(), json!(skip));
}
let response = self.send(op(json!({"Explain": Value::Object(body)})))?;
response.decode("Explain")
}
pub fn document_begin_transaction(&mut self, serializable: bool) -> Result<String> {
#[derive(Deserialize)]
struct Begun {
#[serde(default)]
txn: String,
}
let response = self.send_in(
op(json!({"BeginTransaction": {"serializable": serializable}})),
None,
)?;
let out: Begun = response.decode("BeginTransaction")?;
if out.txn.is_empty() {
return Err(Error::protocol(
"BeginTransaction answered with no transaction id",
));
}
Ok(out.txn)
}
pub fn document_commit_transaction(&mut self, txn: &str) -> Result<DocumentCommit> {
let outcome = self.send_in(op(json!("CommitTransaction")), Some(txn));
self.forget_txn(txn);
outcome?.decode("CommitTransaction")
}
pub fn document_rollback_transaction(&mut self, txn: &str) -> Result<()> {
let outcome = self.send_in(op(json!("RollbackTransaction")), Some(txn));
self.forget_txn(txn);
outcome.map(|_| ())
}
pub fn document_rebuild_index(
&mut self,
collection: &str,
index_name: &str,
) -> Result<IndexRebuild> {
let response = self.send(op(json!({"RebuildIndex": {
"collection": collection,
"index_name": index_name,
}})))?;
response.decode("RebuildIndex")
}
pub fn document_set_collection_options(
&mut self,
collection: &str,
options: &CollectionOptions,
) -> Result<i64> {
#[derive(Deserialize)]
struct Updated {
#[serde(default)]
documents_checked: i64,
}
let response = self.send(op(json!({"SetCollectionOptions": {
"collection": collection,
"options": options,
}})))?;
let out: Updated = response.decode("SetCollectionOptions")?;
Ok(out.documents_checked)
}
pub fn document_get_collection_options(
&mut self,
collection: &str,
) -> Result<CollectionOptions> {
#[derive(Deserialize)]
struct Wire {
#[serde(default)]
options: CollectionOptions,
}
let response = self.send(op(
json!({"GetCollectionOptions": {"collection": collection}}),
))?;
let out: Wire = response.decode("GetCollectionOptions")?;
Ok(out.options)
}
pub fn document_bulk_write(
&mut self,
collection: &str,
ops: &[BulkOp],
) -> Result<BulkWriteResult> {
let response = self.send(op(json!({"BulkWrite": {
"collection": collection,
"ops": ops,
}})))?;
response.decode("BulkWrite")
}
}
fn object_value(document: &impl Serialize) -> Result<Value> {
let document = serde_json::to_value(document)
.map_err(|e| Error::invalid(format!("the document cannot be encoded as JSON: {e}")))?;
if !document.is_object() {
return Err(Error::invalid(
"a document must encode to a JSON object, not a scalar or an array",
));
}
Ok(document)
}
#[derive(Deserialize)]
struct UpdateOne {
#[serde(default)]
#[allow(dead_code)]
updated: bool,
#[serde(default)]
inserted: bool,
}
fn documents_of(response: &Response, what: &str) -> Result<Vec<Document>> {
let data = response.expect("Documents", what)?;
serde_json::from_value(data.clone())
.map_err(|e| Error::protocol(format!("malformed Documents payload from {what}: {e}")))
}
#[cfg(test)]
mod tests {
use super::*;
fn wire(value: &impl Serialize) -> Value {
serde_json::to_value(value).unwrap()
}
#[test]
fn filters_are_externally_tagged() {
assert_eq!(wire(&DocumentFilter::all()), json!("All"));
assert_eq!(
wire(&DocumentFilter::eq("kind", "widget")),
json!({"Eq": {"field": "kind", "value": "widget"}})
);
assert_eq!(
wire(&DocumentFilter::lt("price", 10)),
json!({"Lt": {"field": "price", "value": 10}})
);
assert_eq!(
wire(&DocumentFilter::is_in("id", ["a", "b"])),
json!({"In": {"field": "id", "values": ["a", "b"]}})
);
}
#[test]
fn and_nests_its_children_in_order() {
let filter = DocumentFilter::and([DocumentFilter::eq("a", 1), DocumentFilter::gt("b", 2)]);
let value = wire(&filter);
assert_eq!(value["And"][0]["Eq"]["field"], "a");
assert_eq!(value["And"][1]["Gt"]["field"], "b");
}
#[test]
fn an_update_omits_the_half_it_does_not_use() {
let update = DocumentUpdate::new().set("name", "ada");
assert_eq!(wire(&update), json!({"set": {"name": "ada"}}));
let update = DocumentUpdate::new().inc("hits", 1);
assert_eq!(wire(&update), json!({"inc": {"hits": 1}}));
assert!(DocumentUpdate::new().is_empty());
}
#[test]
fn pipeline_stages_keep_the_servers_own_names() {
assert_eq!(
wire(&AggregateStage::filter(DocumentFilter::all())),
json!({"Match": "All"})
);
assert_eq!(wire(&AggregateStage::skip(2)), json!({"Skip": 2}));
assert_eq!(wire(&AggregateStage::limit(5)), json!({"Limit": 5}));
assert_eq!(
wire(&AggregateStage::count("n")),
json!({"Count": {"field": "n"}})
);
assert_eq!(
wire(&AggregateStage::project(["a", "b"], true)),
json!({"Project": {"fields": ["a", "b"], "include": true}})
);
}
#[test]
fn a_group_stage_carries_its_key_and_accumulators() {
let stage = AggregateStage::group(
GroupKey::field("customer"),
[
GroupAccumulator::new("total", AccumulatorOp::sum("amount")),
GroupAccumulator::new("orders", AccumulatorOp::count()),
],
);
let value = wire(&stage);
assert_eq!(value["Group"]["by"], json!({"Field": "customer"}));
assert_eq!(value["Group"]["accumulators"][0]["output"], "total");
assert_eq!(
value["Group"]["accumulators"][0]["op"],
json!({"Sum": "amount"})
);
assert_eq!(value["Group"]["accumulators"][1]["op"], json!("Count"));
}
#[test]
fn sort_keys_say_which_way_they_go() {
assert_eq!(
wire(&AggregateStage::sort([SortKey::descending("total")])),
json!({"Sort": [{"field": "total", "descending": true}]})
);
assert_eq!(
wire(&SortKey::ascending("name")),
json!({"field": "name", "descending": false})
);
}
}