mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-24 01:01:37 +00:00
fix(mongodb): support ObjectId filters by _id
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user