use bson::{Bson, doc};
use vantage_expressions::Expressive;
use crate::condition::MongoCondition;
use crate::types::{AnyMongoType, MongoType};
fn field_name<T>(expr: &(impl Expressive<T> + ?Sized)) -> String {
expr.expr().template.clone()
}
fn to_bson_val(value: impl Into<AnyMongoType>) -> Bson {
let any: AnyMongoType = value.into();
any.to_bson()
}
fn negate(cond: MongoCondition) -> MongoCondition {
match cond {
MongoCondition::Doc(doc) => {
let mut negated = bson::Document::new();
for (key, val) in doc {
match val {
Bson::Document(inner) => {
negated.insert(key, doc! { "$not": inner });
}
other => {
negated.insert(key, doc! { "$not": { "$eq": other } });
}
}
}
MongoCondition::Doc(negated)
}
MongoCondition::And(conditions) => {
MongoCondition::And(conditions.into_iter().map(negate).collect())
}
other => other,
}
}
pub trait MongoOperation<T>: Expressive<T> {
fn eq(&self, value: impl Into<AnyMongoType>) -> MongoCondition
where
Self: Sized,
{
MongoCondition::Doc(doc! { field_name(self): { "$eq": to_bson_val(value) } })
}
fn ne(&self, value: impl Into<AnyMongoType>) -> MongoCondition
where
Self: Sized,
{
MongoCondition::Doc(doc! { field_name(self): { "$ne": to_bson_val(value) } })
}
fn gt(&self, value: impl Into<AnyMongoType>) -> MongoCondition
where
Self: Sized,
{
MongoCondition::Doc(doc! { field_name(self): { "$gt": to_bson_val(value) } })
}
fn gte(&self, value: impl Into<AnyMongoType>) -> MongoCondition
where
Self: Sized,
{
MongoCondition::Doc(doc! { field_name(self): { "$gte": to_bson_val(value) } })
}
fn lt(&self, value: impl Into<AnyMongoType>) -> MongoCondition
where
Self: Sized,
{
MongoCondition::Doc(doc! { field_name(self): { "$lt": to_bson_val(value) } })
}
fn lte(&self, value: impl Into<AnyMongoType>) -> MongoCondition
where
Self: Sized,
{
MongoCondition::Doc(doc! { field_name(self): { "$lte": to_bson_val(value) } })
}
fn in_<I, V>(&self, values: I) -> MongoCondition
where
Self: Sized,
I: IntoIterator<Item = V>,
V: Into<AnyMongoType>,
{
let arr: Vec<Bson> = values.into_iter().map(to_bson_val).collect();
MongoCondition::Doc(doc! { field_name(self): { "$in": arr } })
}
}
impl<T, S: Expressive<T>> MongoOperation<T> for S {}
impl Expressive<AnyMongoType> for MongoCondition {
fn expr(&self) -> vantage_expressions::Expression<AnyMongoType> {
vantage_expressions::Expression::new(format!("{:?}", self), vec![])
}
}
impl MongoCondition {
pub fn eq_bool(&self, value: bool) -> MongoCondition {
if value {
self.clone()
} else {
negate(self.clone())
}
}
pub fn ne_bool(&self, value: bool) -> MongoCondition {
if value {
negate(self.clone())
} else {
self.clone()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use vantage_table::column::core::Column;
#[test]
fn test_column_eq() {
let name = Column::<String>::new("name");
let cond = name.eq("Alice");
match cond {
MongoCondition::Doc(doc) => {
assert_eq!(doc, doc! { "name": { "$eq": "Alice" } });
}
_ => panic!("expected Doc"),
}
}
#[test]
fn test_column_gt() {
let price = Column::<i64>::new("price");
let cond = price.gt(100i64);
match cond {
MongoCondition::Doc(doc) => {
assert_eq!(doc, doc! { "price": { "$gt": 100i64 } });
}
_ => panic!("expected Doc"),
}
}
#[test]
fn test_column_in() {
let status = Column::<String>::new("status");
let cond = status.in_(vec!["active", "pending"]);
match cond {
MongoCondition::Doc(doc) => {
assert_eq!(doc, doc! { "status": { "$in": ["active", "pending"] } });
}
_ => panic!("expected Doc"),
}
}
#[test]
fn test_chaining_gt_eq_false() {
let price = Column::<i64>::new("price");
let cond = price.gt(10i64).eq_bool(false);
match cond {
MongoCondition::Doc(doc) => {
assert_eq!(doc, doc! { "price": { "$not": { "$gt": 10i64 } } });
}
_ => panic!("expected Doc"),
}
}
#[test]
fn test_chaining_gt_eq_true() {
let price = Column::<i64>::new("price");
let cond = price.gt(10i64).eq_bool(true);
match cond {
MongoCondition::Doc(doc) => {
assert_eq!(doc, doc! { "price": { "$gt": 10i64 } });
}
_ => panic!("expected Doc"),
}
}
#[test]
fn test_negate_simple_value() {
let cond = MongoCondition::Doc(doc! { "active": true });
let negated = negate(cond);
match negated {
MongoCondition::Doc(doc) => {
assert_eq!(doc, doc! { "active": { "$not": { "$eq": true } } });
}
_ => panic!("expected Doc"),
}
}
#[test]
fn test_negate_operator() {
let cond = MongoCondition::Doc(doc! { "price": { "$gt": 100 } });
let negated = negate(cond);
match negated {
MongoCondition::Doc(doc) => {
assert_eq!(doc, doc! { "price": { "$not": { "$gt": 100 } } });
}
_ => panic!("expected Doc"),
}
}
#[test]
fn test_condition_is_correct_type() {
let price = Column::<i64>::new("price");
let cond: MongoCondition = price.gt(100i64);
let _: MongoCondition = cond;
}
}