Skip to main content

vantage_expressions/expression/
mapping.rs

1//! Module for mapping Expression types between different value types recursively
2
3use crate::expression::core::Expression;
4use crate::traits::expressive::{DeferredFn, DeferredFuture, ExpressiveEnum};
5
6/// Trait for mapping Expression from one type to another
7pub trait ExpressionMapper<From, To> {
8    /// Convert `Expression<From>` to `Expression<To>`
9    fn map_expression(expr: Expression<From>) -> Expression<To>
10    where
11        From: Into<To> + Send + Clone + 'static,
12        To: Send + 'static;
13}
14
15impl<From, To> ExpressionMapper<From, To> for Expression<From> {
16    fn map_expression(expr: Expression<From>) -> Expression<To>
17    where
18        From: Into<To> + Send + Clone + 'static,
19        To: Send + 'static,
20    {
21        Expression::new(
22            expr.template,
23            expr.parameters
24                .into_iter()
25                .map(|param| map_expressive_enum(param))
26                .collect(),
27        )
28    }
29}
30
31/// Convert `ExpressiveEnum<From>` to `ExpressiveEnum<To>`
32fn map_expressive_enum<From, To>(enum_value: ExpressiveEnum<From>) -> ExpressiveEnum<To>
33where
34    From: Into<To> + Send + Clone + 'static,
35    To: Send + 'static,
36{
37    match enum_value {
38        // Scalar values can be converted directly
39        ExpressiveEnum::Scalar(value) => ExpressiveEnum::Scalar(value.into()),
40
41        // Nested expressions are converted recursively
42        ExpressiveEnum::Nested(expr) => ExpressiveEnum::Nested(Expression::map_expression(expr)),
43
44        // Deferred values need to be wrapped in a conversion closure
45        ExpressiveEnum::Deferred(deferred) => ExpressiveEnum::Deferred(map_deferred_fn(deferred)),
46    }
47}
48
49/// Convert `DeferredFn<From>` to `DeferredFn<To>`
50fn map_deferred_fn<From, To>(deferred: DeferredFn<From>) -> DeferredFn<To>
51where
52    From: Into<To> + Send + Clone + 'static,
53    To: Send + 'static,
54{
55    DeferredFn::new(move || {
56        let deferred = deferred.clone();
57        Box::pin(async move {
58            let result = deferred.call().await?;
59            Ok(map_expressive_enum(result))
60        }) as DeferredFuture<To>
61    })
62}
63
64/// Extension trait to add map method directly to Expression
65pub trait ExpressionMap<From> {
66    /// Map this expression to a different type
67    fn map<To>(self) -> Expression<To>
68    where
69        From: Into<To> + Send + Clone + 'static,
70        To: Send + 'static;
71}
72
73impl<From> ExpressionMap<From> for Expression<From> {
74    fn map<To>(self) -> Expression<To>
75    where
76        From: Into<To> + Send + Clone + 'static,
77        To: Send + 'static,
78    {
79        Expression::map_expression(self)
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86    use crate::traits::datasource::{DataSource, ExprDataSource};
87    use crate::traits::expressive::{DeferredFn, ExpressiveEnum};
88    use serde_json::Value;
89    use vantage_core::Result;
90
91    // Mock String database
92    #[derive(Clone)]
93    struct StringDatabase {
94        result: String,
95    }
96
97    impl StringDatabase {
98        fn new(result: String) -> Self {
99            Self { result }
100        }
101    }
102
103    impl DataSource for StringDatabase {}
104
105    impl ExprDataSource<String> for StringDatabase {
106        async fn execute(&self, _expr: &Expression<String>) -> Result<String> {
107            Ok(self.result.clone())
108        }
109
110        fn defer(&self, _expr: Expression<String>) -> DeferredFn<String>
111        where
112            String: Clone + Send + Sync + 'static,
113        {
114            let result = self.result.clone();
115            DeferredFn::new(move || {
116                let result = result.clone();
117                Box::pin(async move { Ok(ExpressiveEnum::Scalar(result)) })
118            })
119        }
120    }
121
122    // Mock JSON database
123    #[derive(Clone)]
124    struct JsonDatabase {
125        result: Value,
126    }
127
128    impl JsonDatabase {
129        fn new(result: Value) -> Self {
130            Self { result }
131        }
132    }
133
134    impl DataSource for JsonDatabase {}
135
136    impl ExprDataSource<Value> for JsonDatabase {
137        async fn execute(&self, _expr: &Expression<Value>) -> Result<Value> {
138            Ok(self.result.clone())
139        }
140
141        fn defer(&self, _expr: Expression<Value>) -> DeferredFn<Value>
142        where
143            Value: Clone + Send + Sync + 'static,
144        {
145            let result = self.result.clone();
146            DeferredFn::new(move || {
147                let result = result.clone();
148                Box::pin(async move { Ok(ExpressiveEnum::Scalar(result)) })
149            })
150        }
151    }
152
153    #[test]
154    fn test_scalar_mapping() {
155        let string_expr: Expression<String> =
156            Expression::new("age > {}", vec![ExpressiveEnum::Scalar("25".to_string())]);
157        let value_expr: Expression<Value> = string_expr.map();
158
159        assert_eq!(value_expr.template, "age > {}");
160        assert_eq!(value_expr.parameters.len(), 1);
161    }
162
163    #[test]
164    fn test_nested_mapping() {
165        let inner_expr: Expression<String> = Expression::new(
166            "status = {}",
167            vec![ExpressiveEnum::Scalar("active".to_string())],
168        );
169        let outer_expr: Expression<String> = Expression::new(
170            "SELECT * FROM users WHERE {}",
171            vec![ExpressiveEnum::Nested(inner_expr)],
172        );
173
174        let mapped_expr: Expression<Value> = outer_expr.map();
175
176        assert_eq!(mapped_expr.template, "SELECT * FROM users WHERE {}");
177        assert_eq!(mapped_expr.parameters.len(), 1);
178    }
179
180    #[tokio::test]
181    async fn test_deferred_mapping() {
182        let deferred_string =
183            DeferredFn::new(|| Box::pin(async { Ok(ExpressiveEnum::Scalar("test".to_string())) }));
184
185        let string_expr: Expression<String> = Expression::new(
186            "SELECT * WHERE name = {}",
187            vec![ExpressiveEnum::Deferred(deferred_string)],
188        );
189
190        let value_expr: Expression<Value> = string_expr.map();
191
192        assert_eq!(value_expr.template, "SELECT * WHERE name = {}");
193        assert_eq!(value_expr.parameters.len(), 1);
194
195        // Test that the deferred function still works after mapping
196        if let ExpressiveEnum::Deferred(ref deferred) = value_expr.parameters[0] {
197            let result = deferred.call().await.unwrap();
198            match result {
199                ExpressiveEnum::Scalar(Value::String(s)) => assert_eq!(s, "test"),
200                _ => panic!("Expected string value"),
201            }
202        } else {
203            panic!("Expected deferred parameter");
204        }
205    }
206
207    #[tokio::test]
208    async fn test_cross_database_defer_map() {
209        // Create databases with incompatible value types
210        let db1 = StringDatabase::new("user123,user456".to_string());
211        let db2 = JsonDatabase::new(Value::String("processed".to_string()));
212
213        // Create query for db1
214        let string_query = Expression::new(
215            "SELECT user_ids FROM active_users WHERE department = {}",
216            vec![ExpressiveEnum::Scalar("engineering".to_string())],
217        );
218
219        // Defer the query from db1
220        let deferred_query = db1.defer(string_query);
221
222        // Map the deferred String query to JSON Value and execute on db2
223        let mapped_deferred = DeferredFn::new(move || {
224            let deferred_query = deferred_query.clone();
225            Box::pin(async move {
226                let result = deferred_query.call().await?;
227                Ok(map_expressive_enum(result))
228            })
229        });
230
231        let json_expr = Expression::new(
232            "PROCESS_USERS({})",
233            vec![ExpressiveEnum::Deferred(mapped_deferred)],
234        );
235
236        let result = db2.execute(&json_expr).await;
237        assert_eq!(result.unwrap(), Value::String("processed".to_string()));
238    }
239}