Skip to main content

vantage_mongodb/select/
builder.rs

1//! Builder methods for MongoSelect — produce native MongoDB types
2//! (filter Document, projection Document, sort Document, FindOptions).
3
4use bson::Document;
5
6use super::MongoSelect;
7
8impl MongoSelect {
9    /// Build the filter `Document` by resolving all conditions.
10    pub async fn build_filter(&self) -> vantage_core::Result<Document> {
11        crate::condition::resolve_conditions(self.conditions.iter()).await
12    }
13
14    /// Build the projection `Document`. Returns `None` if all fields requested.
15    pub fn build_projection(&self) -> Option<Document> {
16        if self.fields.is_empty() {
17            return None;
18        }
19        let mut proj = Document::new();
20        for f in &self.fields {
21            proj.insert(f.as_str(), 1);
22        }
23        Some(proj)
24    }
25
26    /// Build the sort `Document`. Returns `None` if no ordering.
27    pub fn build_sort(&self) -> Option<Document> {
28        if self.sort.is_empty() {
29            return None;
30        }
31        let mut doc = Document::new();
32        for (field, dir) in &self.sort {
33            doc.insert(field.as_str(), *dir);
34        }
35        Some(doc)
36    }
37
38    /// Build `mongodb::options::FindOptions` from the current state.
39    pub fn build_find_options(&self) -> mongodb::options::FindOptions {
40        let mut opts = mongodb::options::FindOptions::default();
41        opts.projection = self.build_projection();
42        opts.sort = self.build_sort();
43        opts.limit = self.limit;
44        opts.skip = self.skip.map(|s| s as u64);
45        opts
46    }
47}
48
49#[cfg(test)]
50mod tests {
51    use bson::doc;
52
53    use super::*;
54    use vantage_expressions::Selectable;
55
56    #[test]
57    fn test_build_projection() {
58        let s = MongoSelect::new().with_field("name").with_field("price");
59        let proj = s.build_projection().unwrap();
60        assert_eq!(proj, doc! { "name": 1, "price": 1 });
61    }
62
63    #[test]
64    fn test_build_projection_empty() {
65        let s = MongoSelect::new();
66        assert!(s.build_projection().is_none());
67    }
68
69    #[test]
70    fn test_build_sort() {
71        let mut s = MongoSelect::new();
72        s.sort.push(("price".into(), 1));
73        s.sort.push(("name".into(), -1));
74        let sort = s.build_sort().unwrap();
75        assert_eq!(sort, doc! { "price": 1, "name": -1 });
76    }
77
78    #[test]
79    fn test_build_find_options() {
80        let s = MongoSelect::new()
81            .with_field("name")
82            .with_limit(Some(5), Some(10));
83        let opts = s.build_find_options();
84        assert!(opts.projection.is_some());
85        assert_eq!(opts.limit, Some(5));
86        assert_eq!(opts.skip, Some(10));
87    }
88
89    #[tokio::test]
90    async fn test_build_filter_empty() {
91        let s = MongoSelect::new();
92        let filter = s.build_filter().await.unwrap();
93        assert_eq!(filter, doc! {});
94    }
95
96    #[tokio::test]
97    async fn test_build_filter_single() {
98        let s = MongoSelect::new().with_condition(doc! { "active": true });
99        let filter = s.build_filter().await.unwrap();
100        assert_eq!(filter, doc! { "active": true });
101    }
102
103    #[tokio::test]
104    async fn test_build_filter_multiple() {
105        let s = MongoSelect::new()
106            .with_condition(doc! { "active": true })
107            .with_condition(doc! { "price": { "$gt": 100 } });
108        let filter = s.build_filter().await.unwrap();
109        assert_eq!(
110            filter,
111            doc! { "$and": [{ "active": true }, { "price": { "$gt": 100 } }] }
112        );
113    }
114}