use serde_json::{Value, Map};
pub fn project(doc: &Value, fields: &[Value]) -> Value {
let mut filtered_doc = Map::new();
for field in fields {
if let Some(field_path) = field.as_str() {
let parts: Vec<&str> = field_path.split('.').collect();
if let Some(val) = get_nested_value(doc, &parts) {
insert_nested_value(&mut filtered_doc, &parts, val);
}
}
}
Value::Object(filtered_doc)
}
pub fn get_nested_value(doc: &Value, parts: &[&str]) -> Option<Value> {
let mut current = doc;
for part in parts {
if let Some(v) = current.get(*part) {
current = v;
} else {
return None; }
}
Some(current.clone())
}
fn insert_nested_value(target: &mut Map<String, Value>, parts: &[&str], value: Value) {
if parts.is_empty() { return; }
let key = parts[0].to_string();
if parts.len() == 1 {
target.insert(key, value);
} else {
let next_target = target.entry(key).or_insert_with(|| Value::Object(Map::new()));
if let Some(next_map) = next_target.as_object_mut() {
insert_nested_value(next_map, &parts[1..], value);
}
}
}
pub fn exclude(doc: &Value, fields: &[Value]) -> Value {
let mut result = match doc.as_object() {
Some(obj) => obj.clone(),
None => return doc.clone(),
};
for field in fields {
if let Some(field_path) = field.as_str() {
let parts: Vec<&str> = field_path.split('.').collect();
remove_nested_value(&mut result, &parts);
}
}
Value::Object(result)
}
fn remove_nested_value(target: &mut Map<String, Value>, parts: &[&str]) {
if parts.is_empty() { return; }
let key = parts[0];
if parts.len() == 1 {
target.remove(key);
} else if let Some(child) = target.get_mut(key) {
if let Some(child_map) = child.as_object_mut() {
remove_nested_value(child_map, &parts[1..]);
}
if target.get(key).and_then(|v| v.as_object()).map(|o| o.is_empty()).unwrap_or(false) {
target.remove(key);
}
}
}
pub fn evaluate_where(doc: &Value, query: &Value) -> bool {
let query_obj = match query.as_object() {
Some(obj) => obj,
None => return true, };
for (key, condition) in query_obj {
if key == "$or" {
let sub_queries = match condition.as_array() {
Some(arr) => arr,
None => return false,
};
if !sub_queries.iter().any(|sub| evaluate_where(doc, sub)) { return false; }
continue;
}
if key == "$and" {
let sub_queries = match condition.as_array() {
Some(arr) => arr,
None => return false,
};
if !sub_queries.iter().all(|sub| evaluate_where(doc, sub)) { return false; }
continue;
}
let parts: Vec<&str> = key.split('.').collect();
let doc_val_opt = get_nested_value(doc, &parts);
if !condition.is_object() {
if let Some(dv) = &doc_val_opt {
let matches = match (dv, condition) {
(Value::String(a), Value::String(b)) => a.to_lowercase() == b.to_lowercase(),
_ => dv == condition,
};
if !matches { return false; }
} else {
return false;
}
continue;
}
let cond_obj = condition.as_object().unwrap();
let doc_val_ref = doc_val_opt.as_ref().unwrap_or(&Value::Null);
for (op, op_val) in cond_obj {
let passed = match op.as_str() {
"$eq" | "$equals" => match (doc_val_ref, op_val) {
(Value::String(a), Value::String(b)) => a.to_lowercase() == b.to_lowercase(),
_ => doc_val_ref == op_val,
},
"$ne" | "$notEquals" => match (doc_val_ref, op_val) {
(Value::String(a), Value::String(b)) => a.to_lowercase() != b.to_lowercase(),
_ => doc_val_ref != op_val,
},
"$gt" | "$greaterThan" | "$gte" | "$lt" | "$lessThan" | "$lte" => {
if let (Some(d_num), Some(o_num)) = (doc_val_ref.as_f64(), op_val.as_f64()) {
match op.as_str() {
"$gt" | "$greaterThan" => d_num > o_num,
"$gte" => d_num >= o_num,
"$lt" | "$lessThan" => d_num < o_num,
"$lte" => d_num <= o_num,
_ => false,
}
} else {
false
}
},
"$contains" | "$ct" => {
match doc_val_ref {
Value::String(d_str) => {
if let Some(o_str) = op_val.as_str() {
d_str.to_lowercase().contains(&o_str.to_lowercase())
} else {
false
}
}
Value::Array(arr) => arr.contains(op_val),
_ => false,
}
},
"$in" | "$oneOf" => {
if let Some(allowed) = op_val.as_array() {
allowed.iter().any(|v| match (doc_val_ref, v) {
(Value::String(a), Value::String(b)) => a.to_lowercase() == b.to_lowercase(),
_ => doc_val_ref == v,
})
} else {
false
}
},
"$nin" | "$notIn" => {
if let Some(excluded) = op_val.as_array() {
!excluded.iter().any(|v| match (doc_val_ref, v) {
(Value::String(a), Value::String(b)) => a.to_lowercase() == b.to_lowercase(),
_ => doc_val_ref == v,
})
} else {
false
}
},
_ => false,
};
if !passed { return false; }
}
}
true
}