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,
}
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")
}
}
#[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})
);
}
}