vantage_expressions/expression/
mapping.rs1use crate::expression::core::Expression;
4use crate::traits::expressive::{DeferredFn, DeferredFuture, ExpressiveEnum};
5
6pub trait ExpressionMapper<From, To> {
8 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
31fn 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 ExpressiveEnum::Scalar(value) => ExpressiveEnum::Scalar(value.into()),
40
41 ExpressiveEnum::Nested(expr) => ExpressiveEnum::Nested(Expression::map_expression(expr)),
43
44 ExpressiveEnum::Deferred(deferred) => ExpressiveEnum::Deferred(map_deferred_fn(deferred)),
46 }
47}
48
49fn 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
64pub trait ExpressionMap<From> {
66 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 #[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 #[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 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 let db1 = StringDatabase::new("user123,user456".to_string());
211 let db2 = JsonDatabase::new(Value::String("processed".to_string()));
212
213 let string_query = Expression::new(
215 "SELECT user_ids FROM active_users WHERE department = {}",
216 vec![ExpressiveEnum::Scalar("engineering".to_string())],
217 );
218
219 let deferred_query = db1.defer(string_query);
221
222 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}