fix(mongodb): support ObjectId filters by _id

This commit is contained in:
t8y2
2026-06-09 16:42:36 +08:00
parent ee65fa660d
commit 96e11935c2
+141 -3
View File
@@ -117,7 +117,7 @@ pub async fn find_documents(
let filter_doc: Document = match filter {
Some(f) if !f.trim().is_empty() => {
let json: serde_json::Value = serde_json::from_str(f).map_err(|e| format!("Invalid filter JSON: {e}"))?;
json_object_to_document(&json)?
json_filter_to_document(&json)?
}
_ => doc! {},
};
@@ -240,7 +240,7 @@ pub async fn update_documents(
serde_json::from_str(filter_json).map_err(|e| format!("Invalid filter JSON: {e}"))?;
let update_value: serde_json::Value =
serde_json::from_str(update_json).map_err(|e| format!("Invalid update JSON: {e}"))?;
let filter = json_object_to_document(&filter_value).map_err(|e| format!("Invalid filter: {e}"))?;
let filter = json_filter_to_document(&filter_value).map_err(|e| format!("Invalid filter: {e}"))?;
let update = json_object_to_document(&update_value).map_err(|e| format!("Invalid update: {e}"))?;
let col = client.database(database).collection::<Document>(collection);
let result = if many {
@@ -279,7 +279,7 @@ pub async fn delete_documents(
) -> Result<u64, String> {
let filter_value: serde_json::Value =
serde_json::from_str(filter_json).map_err(|e| format!("Invalid filter JSON: {e}"))?;
let filter = json_object_to_document(&filter_value).map_err(|e| format!("Invalid filter: {e}"))?;
let filter = json_filter_to_document(&filter_value).map_err(|e| format!("Invalid filter: {e}"))?;
let col = client.database(database).collection::<Document>(collection);
let result = if many {
col.delete_many(filter).await.map_err(|e| e.to_string())?
@@ -320,6 +320,13 @@ pub fn json_object_to_document(value: &serde_json::Value) -> Result<Document, St
}
}
pub fn json_filter_to_document(value: &serde_json::Value) -> Result<Document, String> {
match json_filter_value_to_bson(value, None) {
Bson::Document(doc) => Ok(doc),
other => Err(format!("Expected a JSON object, got {other:?}")),
}
}
fn json_value_to_bson(value: &serde_json::Value) -> Bson {
match value {
serde_json::Value::Null => Bson::Null,
@@ -350,6 +357,94 @@ fn json_value_to_bson(value: &serde_json::Value) -> Bson {
}
}
fn json_filter_value_to_bson(value: &serde_json::Value, field_name: Option<&str>) -> Bson {
if field_name == Some("_id") {
if let Some(id) = value.as_str() {
return id_equality_bson(id);
}
}
match value {
serde_json::Value::Array(arr) => {
Bson::Array(arr.iter().map(|item| json_filter_value_to_bson(item, None)).collect())
}
serde_json::Value::Object(obj) => {
if obj.len() == 1 {
if let Some(serde_json::Value::String(hex)) = obj.get("$oid") {
if let Ok(oid) = ObjectId::parse_str(hex) {
return Bson::ObjectId(oid);
}
}
}
if field_name == Some("_id") && obj.keys().all(|key| key.starts_with('$')) {
let mut doc = Document::new();
for (key, item) in obj {
match key.as_str() {
"$eq" => {
if let Some(id) = item.as_str() {
doc.insert("$in", object_id_string_variants(id));
} else {
doc.insert(key, json_filter_value_to_bson(item, None));
}
}
"$ne" => {
if let Some(id) = item.as_str() {
doc.insert("$nin", object_id_string_variants(id));
} else {
doc.insert(key, json_filter_value_to_bson(item, None));
}
}
"$in" | "$nin" => {
if let Some(items) = item.as_array() {
doc.insert(key, expand_object_id_string_array(items));
} else {
doc.insert(key, json_filter_value_to_bson(item, None));
}
}
_ => {
doc.insert(key, json_filter_value_to_bson(item, None));
}
}
}
return Bson::Document(doc);
}
let doc: Document = obj.iter().map(|(k, v)| (k.clone(), json_filter_value_to_bson(v, Some(k)))).collect();
Bson::Document(doc)
}
_ => json_value_to_bson(value),
}
}
fn id_equality_bson(id: &str) -> Bson {
let variants = object_id_string_variants(id);
if variants.len() == 1 {
variants.into_iter().next().unwrap_or(Bson::String(id.to_string()))
} else {
Bson::Document(doc! { "$in": variants })
}
}
fn object_id_string_variants(id: &str) -> Vec<Bson> {
match ObjectId::parse_str(id) {
Ok(oid) => vec![Bson::ObjectId(oid), Bson::String(id.to_string())],
Err(_) => vec![Bson::String(id.to_string())],
}
}
fn expand_object_id_string_array(items: &[serde_json::Value]) -> Bson {
let mut values = Vec::new();
for item in items {
if let Some(id) = item.as_str() {
values.extend(object_id_string_variants(id));
} else {
values.push(json_filter_value_to_bson(item, None));
}
}
Bson::Array(values)
}
#[cfg(test)]
mod tests {
use super::*;
@@ -372,4 +467,47 @@ mod tests {
assert_eq!(filters.len(), 1);
assert!(matches!(filters[0].get("_id"), Some(Bson::String(value)) if value == id));
}
#[test]
fn json_filter_to_document_matches_object_id_and_string_for_id_hex() {
let id = "507f1f77bcf86cd799439011";
let filter = serde_json::json!({ "_id": id });
let doc = json_filter_to_document(&filter).unwrap();
let Some(Bson::Document(id_filter)) = doc.get("_id") else {
panic!("expected _id operator document");
};
let Some(Bson::Array(values)) = id_filter.get("$in") else {
panic!("expected _id $in variants");
};
assert!(matches!(values.first(), Some(Bson::ObjectId(_))));
assert!(matches!(values.get(1), Some(Bson::String(value)) if value == id));
}
#[test]
fn json_filter_to_document_expands_id_operator_variants() {
let id = "507f1f77bcf86cd799439011";
let filter = serde_json::json!({ "$and": [{ "_id": { "$eq": id } }] });
let doc = json_filter_to_document(&filter).unwrap();
let Some(Bson::Array(and_items)) = doc.get("$and") else {
panic!("expected $and array");
};
let Some(Bson::Document(first)) = and_items.first() else {
panic!("expected first $and document");
};
let Some(Bson::Document(id_filter)) = first.get("_id") else {
panic!("expected _id operator document");
};
assert!(matches!(id_filter.get("$in"), Some(Bson::Array(values)) if values.len() == 2));
}
#[test]
fn json_filter_to_document_leaves_non_id_hex_strings_alone() {
let id = "507f1f77bcf86cd799439011";
let filter = serde_json::json!({ "owner_id": id });
let doc = json_filter_to_document(&filter).unwrap();
assert!(matches!(doc.get("owner_id"), Some(Bson::String(value)) if value == id));
}
}