vantage_mongodb/
condition.rs1use bson::{Bson, Document};
7use vantage_expressions::{DeferredFn, ExpressiveEnum};
8
9use crate::types::AnyMongoType;
10
11#[derive(Clone)]
20pub enum MongoCondition {
21 Doc(Document),
23 Deferred(DeferredFn<AnyMongoType>),
26 And(Vec<MongoCondition>),
28}
29
30impl MongoCondition {
31 pub fn resolve(
33 &self,
34 ) -> std::pin::Pin<
35 Box<dyn std::future::Future<Output = vantage_core::Result<Document>> + Send + '_>,
36 > {
37 Box::pin(async move {
38 match self {
39 MongoCondition::Doc(doc) => Ok(doc.clone()),
40 MongoCondition::Deferred(deferred) => {
41 let result = deferred.call().await?;
42 let resolved = match result {
43 ExpressiveEnum::Scalar(val) => val,
44 other => {
45 return Err(vantage_core::error!(
46 "MongoCondition::Deferred resolved to non-scalar",
47 result = format!("{:?}", other)
48 ));
49 }
50 };
51 bson_to_document(resolved.into_value())
52 }
53 MongoCondition::And(conditions) => {
54 let mut docs = Vec::with_capacity(conditions.len());
55 for c in conditions {
56 docs.push(c.resolve().await?);
57 }
58 merge_documents(docs)
59 }
60 }
61 })
62 }
63}
64
65impl From<Document> for MongoCondition {
68 fn from(doc: Document) -> Self {
69 MongoCondition::Doc(doc)
70 }
71}
72
73impl std::fmt::Debug for MongoCondition {
74 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 match self {
76 MongoCondition::Doc(doc) => write!(f, "Doc({doc})"),
77 MongoCondition::Deferred(_) => write!(f, "Deferred(...)"),
78 MongoCondition::And(conditions) => f.debug_tuple("And").field(conditions).finish(),
79 }
80 }
81}
82
83fn bson_to_document(value: Bson) -> vantage_core::Result<Document> {
87 match value {
88 Bson::Document(doc) => Ok(doc),
89 other => Err(vantage_core::error!(
90 "Expected Bson::Document from deferred condition",
91 actual = format!("{:?}", other)
92 )),
93 }
94}
95
96fn merge_documents(docs: Vec<Document>) -> vantage_core::Result<Document> {
102 Ok(match docs.len() {
103 0 => Document::new(),
104 1 => docs.into_iter().next().unwrap(),
105 _ => {
106 let array: Vec<Bson> = docs.into_iter().map(Bson::Document).collect();
107 bson::doc! { "$and": array }
108 }
109 })
110}
111
112pub async fn resolve_conditions<'a>(
114 conditions: impl Iterator<Item = &'a MongoCondition>,
115) -> vantage_core::Result<Document> {
116 let mut docs = Vec::new();
117 for c in conditions {
118 docs.push(c.resolve().await?);
119 }
120 merge_documents(docs)
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[test]
128 fn test_from_document() {
129 let doc = bson::doc! { "price": { "$gt": 100 } };
130 let cond: MongoCondition = doc.clone().into();
131 match cond {
132 MongoCondition::Doc(d) => assert_eq!(d, doc),
133 _ => panic!("expected Doc variant"),
134 }
135 }
136
137 #[tokio::test]
138 async fn test_resolve_doc() {
139 let cond = MongoCondition::Doc(bson::doc! { "active": true });
140 let resolved = cond.resolve().await.unwrap();
141 assert_eq!(resolved, bson::doc! { "active": true });
142 }
143
144 #[tokio::test]
145 async fn test_resolve_and() {
146 let cond = MongoCondition::And(vec![
147 bson::doc! { "a": 1 }.into(),
148 bson::doc! { "b": 2 }.into(),
149 ]);
150 let resolved = cond.resolve().await.unwrap();
151 assert_eq!(resolved, bson::doc! { "$and": [{ "a": 1 }, { "b": 2 }] });
152 }
153
154 #[tokio::test]
155 async fn test_resolve_and_single() {
156 let cond = MongoCondition::And(vec![bson::doc! { "x": 1 }.into()]);
157 let resolved = cond.resolve().await.unwrap();
158 assert_eq!(resolved, bson::doc! { "x": 1 });
159 }
160
161 #[tokio::test]
162 async fn test_resolve_and_empty() {
163 let cond = MongoCondition::And(vec![]);
164 let resolved = cond.resolve().await.unwrap();
165 assert_eq!(resolved, bson::doc! {});
166 }
167
168 #[tokio::test]
169 async fn test_resolve_conditions_helper() {
170 let conds = [
171 MongoCondition::Doc(bson::doc! { "a": 1 }),
172 MongoCondition::Doc(bson::doc! { "b": 2 }),
173 ];
174 let resolved = resolve_conditions(conds.iter()).await.unwrap();
175 assert_eq!(resolved, bson::doc! { "$and": [{ "a": 1 }, { "b": 2 }] });
176 }
177
178 #[tokio::test]
179 async fn test_deferred_resolves_document() {
180 let deferred = DeferredFn::new(move || {
181 Box::pin(async move {
182 let doc = bson::doc! { "status": "active" };
183 Ok(ExpressiveEnum::Scalar(AnyMongoType::untyped(
184 Bson::Document(doc),
185 )))
186 })
187 });
188 let cond = MongoCondition::Deferred(deferred);
189 let resolved = cond.resolve().await.unwrap();
190 assert_eq!(resolved, bson::doc! { "status": "active" });
191 }
192
193 #[tokio::test]
194 async fn test_nested_and_with_deferred() {
195 let deferred = DeferredFn::new(move || {
196 Box::pin(async move {
197 let doc = bson::doc! { "owner_id": { "$in": ["a", "b"] } };
198 Ok(ExpressiveEnum::Scalar(AnyMongoType::untyped(
199 Bson::Document(doc),
200 )))
201 })
202 });
203 let cond = MongoCondition::And(vec![
204 bson::doc! { "active": true }.into(),
205 MongoCondition::Deferred(deferred),
206 ]);
207 let resolved = cond.resolve().await.unwrap();
208 assert_eq!(
209 resolved,
210 bson::doc! { "$and": [
211 { "active": true },
212 { "owner_id": { "$in": ["a", "b"] } }
213 ] }
214 );
215 }
216}