use bson::{Bson, Document, doc, oid::ObjectId};
#[derive(Debug, Clone, Default)]
pub struct FilterBuilder {
doc: Document,
}
impl FilterBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn from_doc(doc: Document) -> Self {
Self { doc }
}
pub fn eq(mut self, field: &str, value: impl Into<Bson>) -> Self {
self.doc.insert(field, value.into());
self
}
pub fn ne(mut self, field: &str, value: impl Into<Bson>) -> Self {
self.doc.insert(field, doc! { "$ne": value.into() });
self
}
pub fn gt(mut self, field: &str, value: impl Into<Bson>) -> Self {
self.doc.insert(field, doc! { "$gt": value.into() });
self
}
pub fn gte(mut self, field: &str, value: impl Into<Bson>) -> Self {
self.doc.insert(field, doc! { "$gte": value.into() });
self
}
pub fn lt(mut self, field: &str, value: impl Into<Bson>) -> Self {
self.doc.insert(field, doc! { "$lt": value.into() });
self
}
pub fn lte(mut self, field: &str, value: impl Into<Bson>) -> Self {
self.doc.insert(field, doc! { "$lte": value.into() });
self
}
pub fn in_array(mut self, field: &str, values: Vec<impl Into<Bson>>) -> Self {
let bson_values: Vec<Bson> = values.into_iter().map(Into::into).collect();
self.doc.insert(field, doc! { "$in": bson_values });
self
}
pub fn not_in(mut self, field: &str, values: Vec<impl Into<Bson>>) -> Self {
let bson_values: Vec<Bson> = values.into_iter().map(Into::into).collect();
self.doc.insert(field, doc! { "$nin": bson_values });
self
}
pub fn regex(mut self, field: &str, pattern: &str) -> Self {
self.doc.insert(field, doc! { "$regex": pattern });
self
}
pub fn regex_with_options(mut self, field: &str, pattern: &str, options: &str) -> Self {
self.doc
.insert(field, doc! { "$regex": pattern, "$options": options });
self
}
pub fn exists(mut self, field: &str, exists: bool) -> Self {
self.doc.insert(field, doc! { "$exists": exists });
self
}
pub fn type_is(mut self, field: &str, bson_type: &str) -> Self {
self.doc.insert(field, doc! { "$type": bson_type });
self
}
pub fn size(mut self, field: &str, size: i32) -> Self {
self.doc.insert(field, doc! { "$size": size });
self
}
pub fn all(mut self, field: &str, values: Vec<impl Into<Bson>>) -> Self {
let bson_values: Vec<Bson> = values.into_iter().map(Into::into).collect();
self.doc.insert(field, doc! { "$all": bson_values });
self
}
pub fn elem_match(mut self, field: &str, query: Document) -> Self {
self.doc.insert(field, doc! { "$elemMatch": query });
self
}
pub fn text_search(mut self, search: &str) -> Self {
self.doc.insert("$text", doc! { "$search": search });
self
}
pub fn text_search_with_lang(mut self, search: &str, language: &str) -> Self {
self.doc
.insert("$text", doc! { "$search": search, "$language": language });
self
}
pub fn by_id(mut self, id: ObjectId) -> Self {
self.doc.insert("_id", id);
self
}
pub fn by_id_str(self, id: &str) -> Result<Self, bson::oid::Error> {
let oid = ObjectId::parse_str(id)?;
Ok(self.by_id(oid))
}
pub fn and(mut self, conditions: Vec<Document>) -> Self {
self.doc.insert("$and", conditions);
self
}
pub fn or(mut self, conditions: Vec<Document>) -> Self {
self.doc.insert("$or", conditions);
self
}
pub fn nor(mut self, conditions: Vec<Document>) -> Self {
self.doc.insert("$nor", conditions);
self
}
pub fn not(mut self, field: &str, condition: Document) -> Self {
self.doc.insert(field, doc! { "$not": condition });
self
}
pub fn where_expr(mut self, expression: &str) -> Self {
self.doc.insert("$where", expression);
self
}
pub fn near(
mut self,
field: &str,
longitude: f64,
latitude: f64,
max_distance: Option<f64>,
) -> Self {
let mut geo = doc! {
"$geometry": {
"type": "Point",
"coordinates": [longitude, latitude]
}
};
if let Some(max) = max_distance {
geo.insert("$maxDistance", max);
}
self.doc.insert(field, doc! { "$near": geo });
self
}
pub fn geo_within_box(
mut self,
field: &str,
bottom_left: (f64, f64),
top_right: (f64, f64),
) -> Self {
self.doc.insert(
field,
doc! {
"$geoWithin": {
"$box": [
[bottom_left.0, bottom_left.1],
[top_right.0, top_right.1]
]
}
},
);
self
}
pub fn merge(mut self, other: Document) -> Self {
for (k, v) in other {
self.doc.insert(k, v);
}
self
}
pub fn build(self) -> Document {
self.doc
}
pub fn is_empty(&self) -> bool {
self.doc.is_empty()
}
}
pub fn all() -> Document {
doc! {}
}
pub fn by_id(id: ObjectId) -> Document {
doc! { "_id": id }
}
pub fn by_id_str(id: &str) -> Result<Document, bson::oid::Error> {
let oid = ObjectId::parse_str(id)?;
Ok(doc! { "_id": oid })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_filter_builder_eq() {
let filter = FilterBuilder::new()
.eq("name", "Alice")
.eq("age", 30)
.build();
assert_eq!(filter.get_str("name").unwrap(), "Alice");
assert_eq!(filter.get_i32("age").unwrap(), 30);
}
#[test]
fn test_filter_builder_comparison() {
let filter = FilterBuilder::new().gte("age", 18).lt("age", 65).build();
let _age_lt = filter.get_document("age").unwrap();
}
#[test]
fn test_filter_builder_in_array() {
let filter = FilterBuilder::new()
.in_array("status", vec!["active", "pending"])
.build();
let status = filter.get_document("status").unwrap();
assert!(status.contains_key("$in"));
}
#[test]
fn test_filter_builder_regex() {
let filter = FilterBuilder::new()
.regex("email", r"@example\.com$")
.build();
let email = filter.get_document("email").unwrap();
assert!(email.contains_key("$regex"));
}
#[test]
fn test_filter_builder_or() {
let filter = FilterBuilder::new()
.or(vec![
doc! { "status": "active" },
doc! { "priority": "high" },
])
.build();
assert!(filter.contains_key("$or"));
}
#[test]
fn test_filter_builder_by_id() {
let oid = ObjectId::new();
let filter = FilterBuilder::new().by_id(oid).build();
assert_eq!(filter.get_object_id("_id").unwrap(), oid);
}
#[test]
fn test_filter_builder_text_search() {
let filter = FilterBuilder::new().text_search("hello world").build();
let text = filter.get_document("$text").unwrap();
assert_eq!(text.get_str("$search").unwrap(), "hello world");
}
#[test]
fn test_all_filter() {
let filter = all();
assert!(filter.is_empty());
}
#[test]
fn test_by_id_helper() {
let oid = ObjectId::new();
let filter = by_id(oid);
assert_eq!(filter.get_object_id("_id").unwrap(), oid);
}
}