use crate::expr::Expr;
use crate::field_filters::FieldFilterBuilder;
use crate::field_witnesses::{FieldName, HasField};
use crate::mongo_comparable::{MongoComparable, MongoOrdered};
use crate::path::Path;
use bson;
pub struct FilterBuilder<T> {
prefix: Vec<String>,
clauses: Vec<bson::Document>,
_marker: std::marker::PhantomData<T>,
}
impl<T> FilterBuilder<T> {
pub fn new() -> Self {
Self {
prefix: Vec::new(),
clauses: Vec::new(),
_marker: std::marker::PhantomData,
}
}
fn field_path<F: FieldName>(&self) -> String {
if self.prefix.is_empty() {
F::field_name().to_string()
} else {
format!("{}.{}", self.prefix.join("."), F::field_name())
}
}
pub fn eq<F, V>(&mut self, value: V) -> &mut Self
where
F: FieldName,
T: HasField<F> + MongoComparable<T::Value, V>,
V: Into<bson::Bson> + Clone,
{
let path = self.field_path::<F>();
self.clauses.push(bson::doc! { path: value.into() });
self
}
pub fn regex<F: FieldName>(&mut self, expr: &str, options: Option<&str>) -> &mut Self
where
T: HasField<F>,
{
let path = self.field_path::<F>();
let mut regex_doc = bson::doc! { "$regex": expr };
if let Some(opts) = options
&& !opts.is_empty()
{
regex_doc.insert("$options", opts);
}
self.clauses.push(bson::doc! { path: regex_doc });
self
}
pub fn clauses(&self) -> &Vec<bson::Document> {
&self.clauses
}
pub fn gt<F, V>(&mut self, value: V) -> &mut Self
where
F: FieldName,
T: HasField<F> + MongoComparable<T::Value, V> + MongoOrdered<T::Value, V>,
V: Into<bson::Bson> + Clone,
{
let path = self.field_path::<F>();
self.clauses
.push(bson::doc! { path: { "$gt": value.into() } });
self
}
pub fn lt<F, V>(&mut self, value: V) -> &mut Self
where
F: FieldName,
T: HasField<F> + MongoComparable<T::Value, V> + MongoOrdered<T::Value, V>,
V: Into<bson::Bson> + Clone,
{
let path = self.field_path::<F>();
self.clauses
.push(bson::doc! { path: { "$lt": value.into() } });
self
}
pub fn r#in<F, V>(&mut self, values: Vec<V>) -> &mut Self
where
F: FieldName,
T: HasField<F> + MongoComparable<T::Value, V>,
V: Into<bson::Bson> + Clone,
{
let path = self.field_path::<F>();
let bson_values: Vec<bson::Bson> = values.into_iter().map(|v| v.into()).collect();
self.clauses
.push(bson::doc! { path: { "$in": bson_values } });
self
}
pub fn ne<F, V>(&mut self, value: V) -> &mut Self
where
F: FieldName,
T: HasField<F> + MongoComparable<T::Value, V>,
V: Into<bson::Bson> + Clone,
{
let path = self.field_path::<F>();
self.clauses
.push(bson::doc! { path: { "$ne": value.into() } });
self
}
pub fn gte<F, V>(&mut self, value: V) -> &mut Self
where
F: FieldName,
T: HasField<F> + MongoComparable<T::Value, V> + MongoOrdered<T::Value, V>,
V: Into<bson::Bson> + Clone,
{
let path = self.field_path::<F>();
self.clauses
.push(bson::doc! { path: { "$gte": value.into() } });
self
}
pub fn lte<F, V>(&mut self, value: V) -> &mut Self
where
F: FieldName,
T: HasField<F> + MongoComparable<T::Value, V> + MongoOrdered<T::Value, V>,
V: Into<bson::Bson> + Clone,
{
let path = self.field_path::<F>();
self.clauses
.push(bson::doc! { path: { "$lte": value.into() } });
self
}
pub fn exists<F>(&mut self, exists: bool) -> &mut Self
where
F: FieldName,
T: HasField<F>,
{
let path = self.field_path::<F>();
self.clauses
.push(bson::doc! { path: { "$exists": exists } });
self
}
pub fn nin<F, V>(&mut self, values: Vec<V>) -> &mut Self
where
F: FieldName,
T: HasField<F> + MongoComparable<T::Value, V>,
V: Into<bson::Bson> + Clone,
{
let path = self.field_path::<F>();
let bson_values: Vec<bson::Bson> = values.into_iter().map(|v| v.into()).collect();
self.clauses
.push(bson::doc! { path: { "$nin": bson_values } });
self
}
pub fn untyped<F: FieldName>(&mut self, value: bson::Document) -> &mut Self
where
T: HasField<F>,
{
let path = self.field_path::<F>();
self.clauses.push(bson::doc! { path: value });
self
}
pub fn expr(&mut self, expr: Expr<T, bool>) -> &mut Self {
self.clauses.push(bson::doc! { "$expr": expr.into_bson() });
self
}
pub fn with_lookup<F: FieldName, L, G: FieldName, U: HasField<G>, N>(
&mut self,
lookup: L,
f: N,
) -> &mut Self
where
T: HasField<F>,
L: FnOnce(&Path<F, T, T>) -> Path<G, U, T>,
N: FnOnce(&mut FilterBuilder<U>) -> &mut FilterBuilder<U>,
{
let base_field: Path<F, T, T> = Path {
prefix: self.prefix.clone(),
_marker: std::marker::PhantomData,
};
let resolved_field = lookup(&base_field);
let mut nested_builder = FilterBuilder::<U> {
prefix: resolved_field.prefix.clone(),
clauses: vec![],
_marker: std::marker::PhantomData,
};
f(&mut nested_builder);
self.clauses.extend(nested_builder.clauses);
self
}
pub fn with_field<F: FieldName, N>(&mut self, f: N) -> &mut Self
where
T: HasField<F>,
N: FnOnce(&mut FilterBuilder<T>) -> &mut FilterBuilder<T>,
{
self.with_lookup::<F, _, F, T, _>(
|path| Path {
prefix: path.prefix.clone(),
_marker: std::marker::PhantomData,
},
f,
)
}
pub fn or<F, V: IntoIterator, N>(&mut self, input: V, f: N) -> &mut Self
where
F: FieldName,
T: HasField<F>,
N: Fn(&mut FilterBuilder<T>, V::Item) -> &mut FilterBuilder<T>,
{
let mut nested = empty::<T>();
let mut or_clauses: Vec<bson::Document> = vec![];
for value in input {
f(&mut nested, value);
match nested.clauses.len() {
0 => continue, 1 => or_clauses.push(nested.clauses.clone().into_iter().next().unwrap()),
_ => or_clauses.extend(nested.clauses.clone()),
}
nested.clauses.clear(); }
self.clauses.push(bson::doc! { "$or": or_clauses });
self
}
pub fn not<F, B>(&mut self, f: B) -> &mut Self
where
F: FieldName,
T: HasField<F>,
B: FnOnce(FieldFilterBuilder<F, T>) -> FieldFilterBuilder<F, T>,
{
let prepared_ops = f(FieldFilterBuilder::new()).build();
let bson_path = self.field_path::<F>();
if let Ok(ops) = prepared_ops.get_document(F::field_name()) {
self.clauses
.push(bson::doc! { bson_path: bson::doc! { "$not": ops.clone() } });
}
self
}
pub fn and(&self) -> bson::Document {
if self.clauses.is_empty() {
bson::doc! {}
} else if self.clauses.len() == 1 {
self.clauses[0].clone()
} else {
bson::doc! { "$and": self.clauses.clone() }
}
}
}
impl<T> Default for FilterBuilder<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> From<FilterBuilder<T>> for bson::Document {
fn from(val: FilterBuilder<T>) -> Self {
val.and()
}
}
pub fn empty<T>() -> FilterBuilder<T> {
FilterBuilder::new()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::field_witnesses::FieldName;
struct Name;
impl FieldName for Name {
fn field_name() -> &'static str {
"Name"
}
}
struct Category;
impl FieldName for Category {
fn field_name() -> &'static str {
"Category"
}
}
struct TestStruct;
#[test]
fn test_field_path_empty_prefix() {
let builder = FilterBuilder::<TestStruct>::new();
let path = builder.field_path::<Name>();
assert_eq!(path, "Name");
}
#[test]
fn test_field_path_single_prefix() {
let mut builder = FilterBuilder::<TestStruct>::new();
builder.prefix = vec!["user".to_string()];
let path = builder.field_path::<Name>();
assert_eq!(path, "user.Name");
}
#[test]
fn test_field_path_multiple_prefix() {
let mut builder = FilterBuilder::<TestStruct>::new();
builder.prefix = vec!["profile".to_string(), "details".to_string()];
let path = builder.field_path::<Category>();
assert_eq!(path, "profile.details.Category");
}
#[test]
fn test_field_path_nested_deep() {
let mut builder = FilterBuilder::<TestStruct>::new();
builder.prefix = vec![
"root".to_string(),
"level1".to_string(),
"level2".to_string(),
"level3".to_string(),
];
let path = builder.field_path::<Name>();
assert_eq!(path, "root.level1.level2.level3.Name");
}
}