use bson::{Bson, Document};
use vantage_expressions::{DeferredFn, ExpressiveEnum};
use crate::types::AnyMongoType;
#[derive(Clone)]
pub enum MongoCondition {
Doc(Document),
Deferred(DeferredFn<AnyMongoType>),
And(Vec<MongoCondition>),
}
impl MongoCondition {
pub fn resolve(
&self,
) -> std::pin::Pin<
Box<dyn std::future::Future<Output = vantage_core::Result<Document>> + Send + '_>,
> {
Box::pin(async move {
match self {
MongoCondition::Doc(doc) => Ok(doc.clone()),
MongoCondition::Deferred(deferred) => {
let result = deferred.call().await?;
let resolved = match result {
ExpressiveEnum::Scalar(val) => val,
other => {
return Err(vantage_core::error!(
"MongoCondition::Deferred resolved to non-scalar",
result = format!("{:?}", other)
));
}
};
bson_to_document(resolved.into_value())
}
MongoCondition::And(conditions) => {
let mut docs = Vec::with_capacity(conditions.len());
for c in conditions {
docs.push(c.resolve().await?);
}
merge_documents(docs)
}
}
})
}
}
impl From<Document> for MongoCondition {
fn from(doc: Document) -> Self {
MongoCondition::Doc(doc)
}
}
impl std::fmt::Debug for MongoCondition {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MongoCondition::Doc(doc) => write!(f, "Doc({doc})"),
MongoCondition::Deferred(_) => write!(f, "Deferred(...)"),
MongoCondition::And(conditions) => f.debug_tuple("And").field(conditions).finish(),
}
}
}
fn bson_to_document(value: Bson) -> vantage_core::Result<Document> {
match value {
Bson::Document(doc) => Ok(doc),
other => Err(vantage_core::error!(
"Expected Bson::Document from deferred condition",
actual = format!("{:?}", other)
)),
}
}
fn merge_documents(docs: Vec<Document>) -> vantage_core::Result<Document> {
Ok(match docs.len() {
0 => Document::new(),
1 => docs.into_iter().next().unwrap(),
_ => {
let array: Vec<Bson> = docs.into_iter().map(Bson::Document).collect();
bson::doc! { "$and": array }
}
})
}
pub async fn resolve_conditions<'a>(
conditions: impl Iterator<Item = &'a MongoCondition>,
) -> vantage_core::Result<Document> {
let mut docs = Vec::new();
for c in conditions {
docs.push(c.resolve().await?);
}
merge_documents(docs)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_from_document() {
let doc = bson::doc! { "price": { "$gt": 100 } };
let cond: MongoCondition = doc.clone().into();
match cond {
MongoCondition::Doc(d) => assert_eq!(d, doc),
_ => panic!("expected Doc variant"),
}
}
#[tokio::test]
async fn test_resolve_doc() {
let cond = MongoCondition::Doc(bson::doc! { "active": true });
let resolved = cond.resolve().await.unwrap();
assert_eq!(resolved, bson::doc! { "active": true });
}
#[tokio::test]
async fn test_resolve_and() {
let cond = MongoCondition::And(vec![
bson::doc! { "a": 1 }.into(),
bson::doc! { "b": 2 }.into(),
]);
let resolved = cond.resolve().await.unwrap();
assert_eq!(resolved, bson::doc! { "$and": [{ "a": 1 }, { "b": 2 }] });
}
#[tokio::test]
async fn test_resolve_and_single() {
let cond = MongoCondition::And(vec![bson::doc! { "x": 1 }.into()]);
let resolved = cond.resolve().await.unwrap();
assert_eq!(resolved, bson::doc! { "x": 1 });
}
#[tokio::test]
async fn test_resolve_and_empty() {
let cond = MongoCondition::And(vec![]);
let resolved = cond.resolve().await.unwrap();
assert_eq!(resolved, bson::doc! {});
}
#[tokio::test]
async fn test_resolve_conditions_helper() {
let conds = [
MongoCondition::Doc(bson::doc! { "a": 1 }),
MongoCondition::Doc(bson::doc! { "b": 2 }),
];
let resolved = resolve_conditions(conds.iter()).await.unwrap();
assert_eq!(resolved, bson::doc! { "$and": [{ "a": 1 }, { "b": 2 }] });
}
#[tokio::test]
async fn test_deferred_resolves_document() {
let deferred = DeferredFn::new(move || {
Box::pin(async move {
let doc = bson::doc! { "status": "active" };
Ok(ExpressiveEnum::Scalar(AnyMongoType::untyped(
Bson::Document(doc),
)))
})
});
let cond = MongoCondition::Deferred(deferred);
let resolved = cond.resolve().await.unwrap();
assert_eq!(resolved, bson::doc! { "status": "active" });
}
#[tokio::test]
async fn test_nested_and_with_deferred() {
let deferred = DeferredFn::new(move || {
Box::pin(async move {
let doc = bson::doc! { "owner_id": { "$in": ["a", "b"] } };
Ok(ExpressiveEnum::Scalar(AnyMongoType::untyped(
Bson::Document(doc),
)))
})
});
let cond = MongoCondition::And(vec![
bson::doc! { "active": true }.into(),
MongoCondition::Deferred(deferred),
]);
let resolved = cond.resolve().await.unwrap();
assert_eq!(
resolved,
bson::doc! { "$and": [
{ "active": true },
{ "owner_id": { "$in": ["a", "b"] } }
] }
);
}
}