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)
)),
}
}
pub(crate) 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)
}